Author

Surya

Browsing

DateTime is a common utility in Android. These days most of the project required date and time formatting in Android. So we are building a plug and play solution. I hope each developer will keep this utility in own inbox for time-saving purpose. In this tutorial, we demonstrate different-different date formatting and parsing

SimpleDateFormat is a java class which extends the DateFormat java class. It is used for normalization, formatting and parsing date in a locale-sensitive manner.

SimpleDateFormat is used for below utility in Android

  • Formatting – It allows for formatting Date to Text in Android any given locale.
  • Parsing – You can parse any Text in Date object by using SimpleDateFormat

Some specific Letter is used to representing Date or Time Component in Android. You can read the Google Official site.

Formatting

Here we discuss some date format my intention is to cover each combination of date format.

DATE_FORMAT_1 = hh:mm a
The output will be -: 10:37 am
 
DATE_FORMAT_2 = h:mm a
Output will be -: 10:37 am
 
DATE_FORMAT_3 = yyyy-MM-dd
The output will be -: 2018-12-05
 
DATE_FORMAT_4 = dd-MMMM-yyyy
The output will be -: 05-December-2018
 
DATE_FORMAT_5 = dd MMMM yyyy
The output will be -: 05 December 2018
 
DATE_FORMAT_6 = dd MMMM yyyy zzzz
The output will be -: 05 December 2018 UTC
 
DATE_FORMAT_7 = EEE, MMM d, ''yy
The output will be -: Wed, Dec 5, '18
 
DATE_FORMAT_8 = yyyy-MM-dd HH:mm:ss
The Output will be -: 2018-12-05 10:37:43
 
DATE_FORMAT_9 = h:mm a dd MMMM yyyy
The output will be -: 10:37 am 05 December 2018
 
DATE_FORMAT_10 = K:mm a, z
The output will be -: 10:37 am, UTC
 
DATE_FORMAT_11 = hh 'o''clock' a, zzzz
The output will be -: 10 o'clock am, UTC
 
DATE_FORMAT_12 = yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
The output will be -: 2018-12-05T10:37:43.937Z
 
DATE_FORMAT_13 = E, dd MMM yyyy HH:mm:ss z
The output will be -: Wed, 05 Dec 2018 10:37:43 UTC
 
DATE_FORMAT_14 = yyyy.MM.dd G 'at' HH:mm:ss z
The output will be -: 2018.12.05 AD at 10:37:43 UTC
 
DATE_FORMAT_15 = yyyyy.MMMMM.dd GGG hh:mm aaa
The output will be -: 02018.D.05 AD 10:37 am
 
DATE_FORMAT_16 = EEE, d MMM yyyy HH:mm:ss Z
The output will be -: Wed, 5 Dec 2018 10:37:43 +0000
 
DATE_FORMAT_17 = yyyy-MM-dd'T'HH:mm:ss.SSSZ
The output will be -: 2018-12-05T10:37:43.946+0000
 
DATE_FORMAT_18 = yyyy-MM-dd'T'HH:mm:ss.SSSXXX
The output will be -: 2018-12-05T10:37:43.949Z
 
DATE_FORMAT_19 = dd-MMM-yyyy
The output will be -: 05-Dec-2018

All the above date format combinations. Now comes utility class.

Get Current Date

Example 1
/**
 * Default date format 
 */
public static final String DATE_FORMAT_2 = "dd-MMM-yyyy";
 
public static String getCurrentDate() {
    SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_1);
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date today = Calendar.getInstance().getTime();
    return dateFormat.format(today);
}

Get current Time

Example 2
/**
 * Default time format
 */
public static final String DATE_FORMAT_1 = "hh:mm a";
 
public static String getCurrentTime() {
    SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_1);
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date today = Calendar.getInstance().getTime();
    return dateFormat.format(today);
}

Get Date and Time from Timestamp

Example 3
  /**
   * 
   * @param time in milliseconds (Timestamp)
   * @param mDateFormat SimpleDateFormat
   * @return
   */
  public static String getDateTimeFromTimeStamp(Long time, String mDateFormat) {
      SimpleDateFormat dateFormat = new SimpleDateFormat(mDateFormat);
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date dateTime = new Date(time);
      return dateFormat.format(dateTime);
  }

How to use it

  /**
   * Get current date example 1
   */
  String mCurrentDate = DateUtils.getCurrentDate();

  /**
   * Get current time example 2
   */
  String mCurrentTime = DateUtils.getCurrentTime();

  /**
   * Date Format example 3
   */
  public static final String DATE_FORMAT = "dd-MMM-yyyy";
  String mDateTime = DateUtils.getDateFromTimeStamp(System.currentTimeMillis(), DATE_FORMAT);

Date Parsing in Android

Let’s take few examples of date parsing

Get Timestamp from DateTime in Android
Example 4
  /**
   * Get Timestamp from date and time
   * @param mDateTime datetime String
   * @param mDateFormat Date Format 
   * @return
   * @throws ParseException
   */
  public static long getTimeStampFromDateTime(String mDateTime, String mDateFormat) throws ParseException {
    SimpleDateFormat dateFormat = new SimpleDateFormat(mDateFormat);
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date date = dateFormat.parse(mDateTime);
    return date.getTime();
  }
Get DateTime from Date instances
Example 5
/**
  * Return  datetime String from date object  
  * @param mDateFormat format of date 
  * @param date date object that you want to parse
  * @return
  */
 public static String formatDateTimeFromDate(String mDateFormat, Date date) {
     if (date == null) {
         return null;
     }
     return DateFormat.format(mDateFormat, date).toString();
 }
Convert DateTime String to another DateTime String format

This is very useful and confusing utility in Android

Example 6
 /**
   * Convert one date format string  to another date format string in android
   *
   * @param inputDateFormat Input SimpleDateFormat
   * @param outputDateFormat Output SimpleDateFormat
   * @param inputDate input Date String
   * @throws ParseException
   */
  public static String formatDateFromDateString(String inputDateFormat, String outputDateFormat,
      String inputDate) throws ParseException {
    Date mParsedDate;
    String mOutputDateString;
    SimpleDateFormat mInputDateFormat =
        new SimpleDateFormat(inputDateFormat, java.util.Locale.getDefault());
    SimpleDateFormat mOutputDateFormat =
        new SimpleDateFormat(outputDateFormat, java.util.Locale.getDefault());
    mParsedDate = mInputDateFormat.parse(inputDate);
    mOutputDateString = mOutputDateFormat.format(mParsedDate);
    return mOutputDateString;
  }
How to use
  /**
   * Date Format example 4
   */
  public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
  String mDateTimeString = "2018-12-05 10:37:43";
  try {
    long mTimestamp = DateUtils.getTimeStampFromDateTime(mDateTimeString, DATE_FORMAT);
  } catch (ParseException e) {
    e.printStackTrace();
  }

  /**
   * Get current datetime example 5
   */
  public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
  String mDateTime = DateUtils.formatDateTimeFromDate(DATE_FORMAT, Calendar.getInstance().getTime());



  /**  Example 6
   * DateTime Input Format
   */
  public static final String DATE_INPUT_FORMAT = "yyyy-MM-dd HH:mm:ss";

  /**
   * DateTime Output Format
   */
  public static final String DATE_OUTPUT_FORMAT = "dd-MMM-yyyy";

  String mDateTimeString = "2018-12-05 10:37:43";

  String mDateTime = null;
  try {
    mDateTime = DateUtils.formatDateFromDateString(DATE_INPUT_FORMAT, DATE_OUTPUT_FORMAT, mDateTimeString);
  } catch (ParseException e) {
    e.printStackTrace();
  }

  The output is - 05-Dec-2018;

The complete utility seems like below

package com.androidwave.datetimeutils.utils;
 
 
import android.text.format.DateFormat;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

  public class DateUtils {

    private static final String TAG = "DateUtils";

    public static final String DATE_FORMAT_1 = "hh:mm a";
    public static final String DATE_FORMAT_2 = "h:mm a";
    public static final String DATE_FORMAT_3 = "yyyy-MM-dd";
    public static final String DATE_FORMAT_4 = "dd-MMMM-yyyy";
    public static final String DATE_FORMAT_5 = "dd MMMM yyyy";
    public static final String DATE_FORMAT_6 = "dd MMMM yyyy zzzz";
    public static final String DATE_FORMAT_7 = "EEE, MMM d, ''yy";
    public static final String DATE_FORMAT_8 = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_FORMAT_9 = "h:mm a dd MMMM yyyy";
    public static final String DATE_FORMAT_10 = "K:mm a, z";
    public static final String DATE_FORMAT_11 = "hh 'o''clock' a, zzzz";
    public static final String DATE_FORMAT_12 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    public static final String DATE_FORMAT_13 = "E, dd MMM yyyy HH:mm:ss z";
    public static final String DATE_FORMAT_14 = "yyyy.MM.dd G 'at' HH:mm:ss z";
    public static final String DATE_FORMAT_15 = "yyyyy.MMMMM.dd GGG hh:mm aaa";
    public static final String DATE_FORMAT_16 = "EEE, d MMM yyyy HH:mm:ss Z";
    public static final String DATE_FORMAT_17 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    public static final String DATE_FORMAT_18 = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";

    public static String getCurrentDate() {
      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_1);
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date today = Calendar.getInstance().getTime();
      return dateFormat.format(today);
    }

    public static String getCurrentTime() {
      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_1);
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date today = Calendar.getInstance().getTime();
      return dateFormat.format(today);
    }

    /**
     * @param time in milliseconds (Timestamp)
     * @param mDateFormat SimpleDateFormat
     */
    public static String getDateTimeFromTimeStamp(Long time, String mDateFormat) {
      SimpleDateFormat dateFormat = new SimpleDateFormat(mDateFormat);
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date dateTime = new Date(time);
      return dateFormat.format(dateTime);
    }

    /**
     * Get Timestamp from date and time
     *
     * @param mDateTime datetime String
     * @param mDateFormat Date Format
     * @throws ParseException
     */
    public static long getTimeStampFromDateTime(String mDateTime, String mDateFormat)
        throws ParseException {
      SimpleDateFormat dateFormat = new SimpleDateFormat(mDateFormat);
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date date = dateFormat.parse(mDateTime);
      return date.getTime();
    }

    /**
     * Return  datetime String from date object
     *
     * @param mDateFormat format of date
     * @param date date object that you want to parse
     */
    public static String formatDateTimeFromDate(String mDateFormat, Date date) {
      if (date == null) {
        return null;
      }
      return DateFormat.format(mDateFormat, date).toString();
    }

    /**
     * Convert one date format string  to another date format string in android
     *
     * @param inputDateFormat Input SimpleDateFormat
     * @param outputDateFormat Output SimpleDateFormat
     * @param inputDate input Date String
     * @throws ParseException
     */
    public static String formatDateFromDateString(String inputDateFormat, String outputDateFormat,
        String inputDate) throws ParseException {
      Date mParsedDate;
      String mOutputDateString;
      SimpleDateFormat mInputDateFormat =
          new SimpleDateFormat(inputDateFormat, java.util.Locale.getDefault());
      SimpleDateFormat mOutputDateFormat =
          new SimpleDateFormat(outputDateFormat, java.util.Locale.getDefault());
      mParsedDate = mInputDateFormat.parse(inputDate);
      mOutputDateString = mOutputDateFormat.format(mParsedDate);
      return mOutputDateString;
    }
  }

As we see Format DateTime in Android with example, I hope this will help you. Happy Coding 😁

Welcome, Buddy… In this tutorial, we demonstrate, How to Build multi-language support apps in the android application. As per android best practice, Application should be kept culture-specific resource. So that resource to be translated to the language based on current locale. In android term, it is called Internationalization and Localization

Localization is play very important role for app reach and user experience (especially GUI). Multi-language support increases your audience reach and provides a better user interface based on locale. Let’s make your Android app more usable and comfortable so more and more audience can use it

How do Internationalization and Localization work in android?

In, Android you can specify resources to the culture of the targeted audience. All content related resource is held in drawable and value folder in Android Project. In this tutorials as will give support of 3 languages Spanish, Hindi (hi_IN)and English(en_US)

Multi Language Supported App in Android (Sample App)

1. Let’s build a sample app in 4 step

1.1 Create a new Project with the following details

In Android Studio, Go to File Menu -> New Project -> Fill some essential details like application name and choose androidx -> Select target SDK -> Select BasicActivity template -> Finish

1.2 Add new resource file strings.xml for the Hindi language

Go to value res folder and right click on go to the value resource file. Select locale from available qualifier clicks the right arrow. Select language and specific region (the region is not mandatory). Check below figure

multi language app android example


Similarly, create the file for Spanish and English locale. So you have to create three locale file for Hindi, English, and Spanish languages

Create a string entry for English locale

<resources>
  <string name="app_name">Multi Language App</string>
  <string name="navigation_drawer_open">Open navigation drawer</string>
  <string name="navigation_drawer_close">Close navigation drawer</string>
  <string name="nav_header_title">Android Wave</string>
  <string name="nav_header_subtitle">androidwave.com</string>
  <string name="nav_header_desc">Android & iOS Developer Blog</string>
  <string name="action_settings">Settings</string>
  <string name="description_androidwave">We demonstrate android & iOS app development tutorials for
    Firebase, Camera2 API, Exo Player, Youtube API, Dagger2, RxJava, MVVM, MVP, Realm, and more.</string>
  <string name="text_hindi" translatable="false">हिंदी</string>
  <string name="text_english" translatable="false">English</string>
  <string name="text_spanish" translatable="false">" Español"</string>

  <! – Navigation Menu-->
  <string name="title_import">Import</string>
  <string name="title_gallery">Gallery</string>
  <string name="title_slideshow">Slideshow</string>
  <string name="title_tools">Tools</string>
  <string name="title_communication">Communicate</string>
  <string name="title_share">Share</string>
  <string name="title_send">Send</string>
</resources>

The same key should be in Hindi locale

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">बहु भाषा ऐप</string>
  <string name="navigation_drawer_open">नेविगेशन ड्रावर को खोले</string>
  <string name="navigation_drawer_close">नेविगेशन ड्रावर को बंद करें </string>
  <string name="nav_header_title">एंड्रॉइड वेव</string>
  <string name="nav_header_subtitle">androidwave.com</string>
  <string name="nav_header_desc">एंड्रॉइड और आईओएस डेवलपर ब्लॉग</string>
  <string name="action_settings">सेटिंग</string>
  <string name="description_androidwave">हम फायरबेस, कैमरा 2 एपीआई, एक्सो प्लेयर, यूट्यूब एपीआई, डैगर 2, आरएक्सजेवा,
    एमवीवीएम, एमवीपी, दायरे, आदि के लिए एंड्रॉइड और आईओएस ऐप डेवलपमेंट ट्यूटोरियल का प्रदर्शन करते हैं।</string>
  <! – Navigation Menu-->
  <string name="title_import">आयात</string>
  <string name="title_gallery">गैलरी</string>
  <string name="title_slideshow">स्लाइड शो</string>
  <string name="title_tools">उपकरण</string>
  <string name="title_communication">संवाद</string>
  <string name="title_share">शेयर</string>
  <string name="title_send">भेजना</string>
</resources>

Furthermore, have to for Spanish

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">Aplicación multi idioma</string>
  <string name="navigation_drawer_open">Cajón de navegación abierto.</string>
  <string name="navigation_drawer_close">Cerrar el cajon de navegacion</string>
  <string name="nav_header_title">Android Wave</string>
  <string name="nav_header_subtitle">androidwave.com</string>
  <string name="nav_header_desc">Android & Blog del desarrollador de iOS</string>
  <string name="action_settings">Ajustes</string>
  <string name="description_androidwave">Demostramos android & Tutoriales de desarrollo de aplicaciones
    iOS para Firebase, Camera2 API, Exo Player, Youtube API, Dagger2, RxJava, MVVM, MVP, Realm y más</string>
  <! – Navigation Menu-->
  <string name="title_import">Importar</string>
  <string name="title_gallery">Galería</string>
  <string name="title_slideshow">Diapositivas</string>
  <string name="title_tools">Herramientas</string>
  <string name="title_communication">Comunicar</string>
  <string name="title_share">Compartir</string>
  <string name="title_send">Enviar</string>
</resources>
The complete project hierarchy looks like this figure
android example

3. Now build LocalManager.java utilities.

3.1. Define some specific constant and define all support locale in LocaleDef annotated interface
  @Retention(RetentionPolicy.SOURCE)
  @StringDef({ ENGLISH, HINDI, SPANISH })
  public @interface LocaleDef {
    String[] SUPPORTED_LOCALES = { ENGLISH, HINDI, SPANISH };
  }

  static final String ENGLISH = "en";
  static final String HINDI = "hi";
  static final String SPANISH = "es";
3.2. Save locale preference in PreferenceManager

Write getter and setter for prefs, It’s setter responsible for persisting data in SharedPreferences and getter are responsible for pulling data from SharedPreferences.

  /**
   * Get saved Locale from SharedPreferences
   *
   * @param mContext current context
   * @return current locale key by default return english locale
   */
  public static String getLanguagePref(Context mContext) {
    SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    return mPreferences.getString(LANGUAGE_KEY, ENGLISH);
  }

  /**
   * set pref key
   */
  private static void setLanguagePref(Context mContext, String localeKey) {
    SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    mPreferences.edit().putString(LANGUAGE_KEY, localeKey).apply();
  }
Now set locale and update the resource
  /**
   * set current pref locale
   */
  public static Context setLocale(Context mContext) {
    return updateResources(mContext, getLanguagePref(mContext));
  }

  /**
   * Set new Locale with context
   */
  public static Context setNewLocale(Context mContext, @LocaleDef String language) {
    setLanguagePref(mContext, language);
    return updateResources(mContext, language);
  }

  /**
   * update resource
   */
  private static Context updateResources(Context context, String language) {
    Locale locale = new Locale(language);
    Locale.setDefault(locale);
    Resources res = context.getResources();
    Configuration config = new Configuration(res.getConfiguration());
    if (Build.VERSION.SDK_INT >= 17) {
      config.setLocale(locale);
      context = context.createConfigurationContext(config);
    } else {
      config.locale = locale;
      res.updateConfiguration(config, res.getDisplayMetrics());
    }
    return context;
  }

Finally, the complete LocaleManager class looks like below

package com.androidwave.multilanguage;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;
import androidx.annotation.StringDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;

public class LocaleManager {

  @Retention(RetentionPolicy.SOURCE)
  @StringDef({ ENGLISH, HINDI, SPANISH })
  public @interface LocaleDef {
    String[] SUPPORTED_LOCALES = { ENGLISH, HINDI, SPANISH };
  }

  static final String ENGLISH = "en";
  static final String HINDI = "hi";
  static final String SPANISH = "es";

  /**
   * SharedPreferences Key
   */
  private static final String LANGUAGE_KEY = "language_key";

  /**
   * set current pref locale
   */
  public static Context setLocale(Context mContext) {
    return updateResources(mContext, getLanguagePref(mContext));
  }

  /**
   * Set new Locale with context
   */
  public static Context setNewLocale(Context mContext, @LocaleDef String language) {
    setLanguagePref(mContext, language);
    return updateResources(mContext, language);
  }

  /**
   * Get saved Locale from SharedPreferences
   *
   * @param mContext current context
   * @return current locale key by default return english locale
   */
  public static String getLanguagePref(Context mContext) {
    SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    return mPreferences.getString(LANGUAGE_KEY, ENGLISH);
  }

  /**
   * set pref key
   */
  private static void setLanguagePref(Context mContext, String localeKey) {
    SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    mPreferences.edit().putString(LANGUAGE_KEY, localeKey).apply();
  }

  /**
   * update resource
   */
  private static Context updateResources(Context context, String language) {
    Locale locale = new Locale(language);
    Locale.setDefault(locale);
    Resources res = context.getResources();
    Configuration config = new Configuration(res.getConfiguration());
    if (Build.VERSION.SDK_INT >= 17) {
      config.setLocale(locale);
      context = context.createConfigurationContext(config);
    } else {
      config.locale = locale;
      res.updateConfiguration(config, res.getDisplayMetrics());
    }
    return context;
  }

  /**
   * get current locale
   */
  public static Locale getLocale(Resources res) {
    Configuration config = res.getConfiguration();
    return Build.VERSION.SDK_INT >= 24 ? config.getLocales().get(0) : config.locale;
  }
}

5. Locale Configuration

Few minor configurations have to need in every activity and application class. For that, I’m creating a base class named is BaseActivity. In this activity, I have written methods for activity title and updating the LocaleManager context.

package com.androidwave.multilanguage;

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import static android.content.pm.PackageManager.GET_META_DATA;

public abstract class BaseActivity extends AppCompatActivity {

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    resetTitles();
  }

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(LocaleManager.setLocale(base));
  }

  protected void resetTitles() {
    try {
      ActivityInfo info = getPackageManager().getActivityInfo(getComponentName(), GET_META_DATA);
      if (info.labelRes != 0) {
        setTitle(info.labelRes);
      }
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }
  }
}

6. Implement Locale Configuration in the Application class

Same things I have to in Application class. Let’s see below code, here we also settings locale on onConfigurationChanged and attachBaseContext.

package com.androidwave.multilanguage;

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;

public class MultiLanguageApp extends Application {

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(LocaleManager.setLocale(base));
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    LocaleManager.setLocale(this);
  }
}

Above all locale configuration is complete and LocaleManager utilities are ready to use. In conclusion, just call below method for change locale of the application

  private void setNewLocale(AppCompatActivity mContext, @LocaleManager.LocaleDef String language) {
    LocaleManager.setNewLocale(this, language);
    Intent intent = mContext.getIntent();
    startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK));
  }
  // En

Implement localization in your android application

Now open the Activity class where you want to change the app language of the app. In application is we mostly written in SettingActivity. But in this android app tutorial we writing in MainActvitiy. Let’s open MainActvitiy and paste below code

package com.androidwave.multilanguage;

import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;

public class MainActivity extends BaseActivity
    implements NavigationView.OnNavigationItemSelectedListener {
  DrawerLayout drawerLayout;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
            .setAction("Action", null).show();
      }
    });

    drawerLayout = findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
        this, drawerLayout, toolbar, R.string.navigation_drawer_open,
        R.string.navigation_drawer_close);
    drawerLayout.addDrawerListener(toggle);
    toggle.syncState();

    NavigationView navigationView = findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(this);
  }

  @Override
  public void onBackPressed() {
    if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
      drawerLayout.closeDrawer(GravityCompat.START);
    } else {
      super.onBackPressed();
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    switch (id) {
      case R.id.local_english:
        setNewLocale(this, LocaleManager.ENGLISH);
        return true;
      case R.id.local_hindi:
        setNewLocale(this, LocaleManager.HINDI);
        return true;

      case R.id.local_spanish:
        setNewLocale(this, LocaleManager.SPANISH);
        return true;
    }

    return super.onOptionsItemSelected(item);
  }

  private void setNewLocale(AppCompatActivity mContext, @LocaleManager.LocaleDef String language) {
    LocaleManager.setNewLocale(this, language);
    Intent intent = mContext.getIntent();
    startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK));
  }

  @SuppressWarnings("StatementWithEmptyBody")
  @Override
  public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    int id = item.getItemId();
    switch (id) {
      case R.id.nav_camera:
        break;
      case R.id.nav_gallery:
        break;
      case R.id.nav_slideshow:
        break;
      case R.id.nav_manage:
        break;
      case R.id.nav_share:
        break;
      case R.id.nav_send:
        break;

      default:
        break;
    }
    drawerLayout.closeDrawer(GravityCompat.START);
    return true;
  }
}

Finally, run the project and change your language. You will get the output as shown in the video.

Conclusion

Thank’s for reading, In this tutorial, we have learned implementation Android Multi Language App Example. I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Keep in touch

If you have any queries, feel free to connect us.

Download Sample Project- Android Multi Language App Example

1. Switch camera rear to front & front-rear in Camera2 API

In the previous 2 blogs, I have explained Video Recoding using Camera2 API. Furthermore, In this tutorial, I’ll explain about switch camera front-rear via-versa. As you we have to build a CameraVideoFragment.java utility class.

2. Declare below variable in CameraVideoFragment.java.

  /**
   * 0 forback camera
   * 1 for front camera
   * Initlity default camera is front camera
   */
  public static final String CAMERA_FRONT = "1";
  public static final String CAMERA_BACK = "0";
  private String cameraId = CAMERA_FRONT;

3. Check available cameras

We can fetch all available cameras from Camera Manager instance by calling getCameraIdList.It returns all available via getCameraIdList() (front/rear), then you can find the best camera that you want to use or suits your needs.

You can get CameraCharacteristics by using camera ID. You can manage facing, resolution, and filter also. In variable declare block, we define front and back camera id.

In this tutorial, the front camera is the default camera.

CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);

As above, private String cameraId = CAMERA_FRONT; we set default camera is front camera, So cameraId have instance of front camera.

4. Furthermore, we can Switch Camera using below code

 public void switchCamera() {
        if (cameraId.equals(CAMERA_FRONT)) {
            cameraId = CAMERA_BACK;
            closeCamera();
            reopenCamera();


        } else if (cameraId.equals(CAMERA_BACK)) {
            cameraId = CAMERA_FRONT;
            closeCamera();
            reopenCamera();

        }
    }

After changing cameraId we just close the previous camera and reopen it.

 public void reopenCamera() {
        if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }
Finally just call switchCamera() method in child Fragment

Place this code and run the project, now you able to see switch camera in camera2 API.

Download Sample Project- Switch Camera in Camera2 API Android

In this post, I will learn about playing YoutubePlayerView in ReyclerView Android. We implement Youtube Player in RecyclerView using WebView with the help of an external library.

Introduction

As usually youtube player API works well in Android in full-screen view. If you want to play a youtube video inside subview, it’s thrown illegal overlay exception for the reason that youtube API is not allowed any kind of overlay, padding, and margin inside the View. Same if you want to play a video in RecylerView similarly Twitter Player Card. In Twitter, Player Card video is played as WebView with some metadata. You no need to learn deep. We have on prebuilt lib for this.

If you facing illegal overlay issue in youtube player, You are in right place. Basically,
YouTube Android Player API is strictly prohibited overlay, So while you play in RecyclerView in then the video will be stopped after few second. Now I explain hows play YoutubePlayer in RecyclerView like Twitter. I’m using a library Android YouTube Player.

This is the fully open source library. The player is run on Webview within IFrame API, is easy to maintain and scalable. This library uses YouTube’s own web player to play videos, to interact with YouTube the library uses the IFrame Player API, inside of a WebView, therefore the YouTube app is not required on the user’s device and there are no issues with YouTube Terms of Service. Let’s learn the implementation step by step

YoutubePlayerView in ReyclerView Sample App

Implementation Steps

1. First of all, create a new project with the name Youtube Player Example.

Go to file menu => Create a new project => Enter a project name and packages name => Select template => Finish

2. Add dependency inside app/build.gradle

Now we need to add below dependency in build.gradle

  • AndroidX – Support lib
  • Glide – For image loading lib
  • ButterKnife – DI View Binding
  • IFrame Player API – Youtube lib
2.1 Define youtube player and butterknife dependency
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    // replace support with androidx
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    // Image loading lib
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
    // Youtube lib
    implementation 'com.github.PierfrancescoSoffritti:AndroidYouTubePlayer:7.0.1'
    // bind view
    implementation 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor "com.jakewharton:butterknife-compiler:10.1.0"

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
2.2 set compile option java sdk target 1.8 for using lambda expression.
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

3. Add INTERNET uses permission in AndroidManifest.xml

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Full project structure look as below figure
how to play youtube player in recylerview

4. Let’s create a model class named YoutubeVideo.java

Define all model entities in POJO such as – id, title, videoId, and imageUrl etc.

package com.androidwave.youtuberecycler.model;

  public class YoutubeVideo {
    private String title;
    private Long id;
    private String videoId;
    private String imageUrl;

    public String getImageUrl() {
      return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
      this.imageUrl = imageUrl;
    }

    public String getTitle() {
      return title;
    }

    public void setTitle(String title) {
      this.title = title;
    }

    public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

    public String getVideoId() {
      return videoId;
    }

    public void setVideoId(String videoId) {
      this.videoId = videoId;
    }
  }

5. BaseViewHolder is helper class for RecyclerView.

Now I’m creating a generics file for BaseViewHolder which extends RecyclerView.ViewHolder. Which help a lot fetching current position of items in RecyclerView Adapter.

package com.androidwave.youtuberecycler.base;

import android.view.View;
import androidx.recyclerview.widget.RecyclerView;

public abstract class BaseViewHolder extends RecyclerView.ViewHolder {

  private int mCurrentPosition;

  public BaseViewHolder(View itemView) {
    super(itemView);
  }

  protected abstract void clear();

  public void onBind(int position) {
    mCurrentPosition = position;
    clear();
  }

  public int getCurrentPosition() {
    return mCurrentPosition;
  }
}

6. Prepare XML layout for list item.

Go to res = > layout folder and creates a layout file named item_youtube_list.xml and paste below code. Finally, the RecyclerView items look like below image

Youtube player in RecyclerView in Android
Youtube Player in RecyclerView tutorial
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
    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="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="6dp"
    app:cardElevation="6dp"
    >

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      >

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:letterSpacing="-0.02"
        android:lineSpacingExtra="5sp"
        android:text="TextView"
        android:textColor="@color/navy"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Was india better then south africa in current serise?"
        />

    <ImageView
        android:id="@+id/imageViewItem"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:scaleType="matrix"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewTitle"
        />

    <com.pierfrancescosoffritti.youtubeplayer.player.YouTubePlayerView
        android:id="@+id/youtube_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewTitle"
        />

    <ImageView
        android:id="@+id/btnPlay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="@+id/imageViewItem"
        app:layout_constraintEnd_toEndOf="@+id/imageViewItem"
        app:layout_constraintStart_toStartOf="@+id/imageViewItem"
        app:layout_constraintTop_toTopOf="@+id/imageViewItem"
        app:srcCompat="@drawable/ic_play_button"
        />
  </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

7. Now create a new class named YoutubeRecyclerAdapter.java.

package com.androidwave.youtuberecycler.adapter;

import android.app.Activity;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.androidwave.youtuberecycler.R;
import com.androidwave.youtuberecycler.base.BaseViewHolder;
import com.androidwave.youtuberecycler.model.YoutubeVideo;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.pierfrancescosoffritti.youtubeplayer.player.AbstractYouTubePlayerListener;
import com.pierfrancescosoffritti.youtubeplayer.player.YouTubePlayerView;
import java.util.List;

public class YoutubeRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {

  public static final int VIEW_TYPE_NORMAL = 1;

  private List<YoutubeVideo> mYoutubeVideos;
  DisplayMetrics displayMetrics = new DisplayMetrics();

  public YoutubeRecyclerAdapter(List<YoutubeVideo> youtubeVideos) {
    mYoutubeVideos = youtubeVideos;
  }

  @NonNull
  @Override
  public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new ViewHolder(LayoutInflater.from(parent.getContext())
        .inflate(R.layout.item_youtube_list, parent, false));
  }

  @Override
  public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
    holder.onBind(position);
  }

  @Override
  public int getItemViewType(int position) {
    return VIEW_TYPE_NORMAL;
  }

  @Override
  public int getItemCount() {
    if (mYoutubeVideos != null && mYoutubeVideos.size() > 0) {
      return mYoutubeVideos.size();
    } else {
      return 1;
    }
  }

  public void setItems(List<YoutubeVideo> youtubeVideos) {
    mYoutubeVideos = youtubeVideos;
    notifyDataSetChanged();
  }

  public class ViewHolder extends BaseViewHolder {
    @BindView(R.id.textViewTitle)
    TextView textWaveTitle;
    @BindView(R.id.btnPlay)
    ImageView playButton;

    @BindView(R.id.imageViewItem)
    ImageView imageViewItems;
    @BindView(R.id.youtube_view)
    YouTubePlayerView youTubePlayerView;

    public ViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
    }

    protected void clear() {

    }

    public void onBind(int position) {
      super.onBind(position);
      final YoutubeVideo mYoutubeVideo = mYoutubeVideos.get(position);
      ((Activity) itemView.getContext()).getWindowManager()
          .getDefaultDisplay()
          .getMetrics(displayMetrics);
      int width = displayMetrics.widthPixels;
      if (mYoutubeVideo.getTitle() != null) {
        textWaveTitle.setText(mYoutubeVideo.getTitle());
      }

      if (mYoutubeVideo.getImageUrl() != null) {
        Glide.with(itemView.getContext())
            .load(mYoutubeVideo.getImageUrl()).
            apply(new RequestOptions().override(width - 36, 200))
            .into(imageViewItems);
      }
      imageViewItems.setVisibility(View.VISIBLE);
      playButton.setVisibility(View.VISIBLE);
      youTubePlayerView.setVisibility(View.GONE);

      playButton.setOnClickListener(view -> {
        imageViewItems.setVisibility(View.GONE);
        youTubePlayerView.setVisibility(View.VISIBLE);
        playButton.setVisibility(View.GONE);
        youTubePlayerView.initialize(
            initializedYouTubePlayer -> initializedYouTubePlayer.addListener(
                new AbstractYouTubePlayerListener() {
                  @Override
                  public void onReady() {
                    initializedYouTubePlayer.loadVideo(mYoutubeVideo.getVideoId(), 0);
                  }
                }), true);
      });
    }
  }
}

8. Open the Activity (MainActivity) and bind the views using @inject annotation using butter knife library.

@BindView(R.id.recyclerViewFeed)
RecyclerView recyclerViewFeed;
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

9. Prepare demo content for list.

private List<YoutubeVideo> prepareList() {
    ArrayList<YoutubeVideo> videoArrayList=new ArrayList<>();

    // add first item
    YoutubeVideo video1 = new YoutubeVideo();
    video1.setId(1l);
    video1.setImageUrl("https://i.ytimg.com/vi/zI-Pux4uaqM/maxresdefault.jpg");
    video1.setTitle(
        "Thugs Of Hindostan - Official Trailer | Amitabh Bachchan | Aamir Khan");
    video1.setVideoId("zI-Pux4uaqM");
    videoArrayList.add(video1);

    // add second item
    YoutubeVideo video2 = new YoutubeVideo();
    video2.setId(2l);
    video2.setImageUrl("https://i.ytimg.com/vi/8ZK_S-46KwE/maxresdefault.jpg");
    video2.setTitle(
        "Colors for Children to Learning with Baby Fun Play with Color Balls Dolphin...");
    video2.setVideoId("8ZK_S-46KwE");

    // add third item
    YoutubeVideo video3 = new YoutubeVideo();
    video3.setId(3l);
    video3.setImageUrl("https://i.ytimg.com/vi/8czMWUH7vW4/hqdefault.jpg");
    video3.setTitle("Air Hostess Accepts Marriage Proposal Mid-Air, Airline Fires her.");
    video3.setVideoId("8czMWUH7vW4");

    // add four item
    YoutubeVideo video4 = new YoutubeVideo();
    video4.setId(4l);
    video4.setImageUrl("https://i.ytimg.com/vi/YrQVYEb6hcc/maxresdefault.jpg");
    video4.setTitle("EXPERIMENT Glowing 1000 degree METAL BALL vs Gunpowder (100 grams)");
    video4.setVideoId("YrQVYEb6hcc");

    // add four item
    YoutubeVideo video5 = new YoutubeVideo();
    video5.setId(5l);
    video5.setImageUrl("https://i.ytimg.com/vi/S84Fuo2rGoY/maxresdefault.jpg");
    video5.setTitle("What happened after Jauhar of Padmavati");
    video5.setVideoId("S84Fuo2rGoY");

    videoArrayList.add(video1);
    videoArrayList.add(video2);
    videoArrayList.add(video3);
    videoArrayList.add(video4);
    return videoArrayList;
  }

10. Set the adapter over RecyclerView.

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    // prepare data for list
    List<YoutubeVideo> youtubeVideos = prepareList();
    mRecyclerAdapter = new YoutubeRecyclerAdapter(youtubeVideos);
    RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
    recyclerViewFeed.setLayoutManager(mLayoutManager);
    recyclerViewFeed.setItemAnimator(new DefaultItemAnimator());
    recyclerViewFeed.setAdapter(mRecyclerAdapter);
  }

11. Finally, MainActivity.java source look like this

package com.androidwave.youtuberecycler;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.androidwave.youtuberecycler.adapter.YoutubeRecyclerAdapter;
import com.androidwave.youtuberecycler.model.YoutubeVideo;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.recyclerViewFeed)
  RecyclerView recyclerViewFeed;

  YoutubeRecyclerAdapter mRecyclerAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    // prepare data for list
    List<YoutubeVideo> youtubeVideos = prepareList();
    mRecyclerAdapter = new YoutubeRecyclerAdapter(youtubeVideos);
    RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
    recyclerViewFeed.setLayoutManager(mLayoutManager);
    recyclerViewFeed.setItemAnimator(new DefaultItemAnimator());
    recyclerViewFeed.setAdapter(mRecyclerAdapter);
  }

  private List<YoutubeVideo> prepareList() {
    ArrayList<YoutubeVideo> videoArrayList=new ArrayList<>();

    // add first item
    YoutubeVideo video1 = new YoutubeVideo();
    video1.setId(1l);
    video1.setImageUrl("https://i.ytimg.com/vi/zI-Pux4uaqM/maxresdefault.jpg");
    video1.setTitle(
        "Thugs Of Hindostan - Official Trailer | Amitabh Bachchan | Aamir Khan");
    video1.setVideoId("zI-Pux4uaqM");
    videoArrayList.add(video1);

    // add second item
    YoutubeVideo video2 = new YoutubeVideo();
    video2.setId(2l);
    video2.setImageUrl("https://i.ytimg.com/vi/8ZK_S-46KwE/maxresdefault.jpg");
    video2.setTitle(
        "Colors for Children to Learning with Baby Fun Play with Color Balls Dolphin...");
    video2.setVideoId("8ZK_S-46KwE");

    // add third item
    YoutubeVideo video3 = new YoutubeVideo();
    video3.setId(3l);
    video3.setImageUrl("https://i.ytimg.com/vi/8czMWUH7vW4/hqdefault.jpg");
    video3.setTitle("Air Hostess Accepts Marriage Proposal Mid-Air, Airline Fires her.");
    video3.setVideoId("8czMWUH7vW4");

    // add four item
    YoutubeVideo video4 = new YoutubeVideo();
    video4.setId(4l);
    video4.setImageUrl("https://i.ytimg.com/vi/YrQVYEb6hcc/maxresdefault.jpg");
    video4.setTitle("EXPERIMENT Glowing 1000 degree METAL BALL vs Gunpowder (100 grams)");
    video4.setVideoId("YrQVYEb6hcc");

    // add four item
    YoutubeVideo video5 = new YoutubeVideo();
    video5.setId(5l);
    video5.setImageUrl("https://i.ytimg.com/vi/S84Fuo2rGoY/maxresdefault.jpg");
    video5.setTitle("What happened after Jauhar of Padmavati");
    video5.setVideoId("S84Fuo2rGoY");

    videoArrayList.add(video1);
    videoArrayList.add(video2);
    videoArrayList.add(video3);
    videoArrayList.add(video4);
    return videoArrayList;
  }
}

Finally, we have learned youtube player in RecyclerView with the help of this example. Thanks a lot

YoutubePlayerView in RecyclerView Source Code

We demonstrate video streaming from the server using the ExoPlayer. In fact, youtube player also used ExoPlayer for streaming video. This ExoPlayer android example I will explain how to use ExoPlayer in own android application.

ExoPlayer is an open-source project. This is not a part of Android SDK. In Android, ExoPlayer is application level media player. It’s standard audio and video components are built on Android’s MediaCodec API, which was released in Android.

ExoPlayer is easy to use, maintainable and fully customize.

ExoPlayer Android Tutorial Sample App

1. Create a new project.

Go to File Menu and Create a New Project, fill information like project and package name after that click Next and select target SDK and Finish

2. After that add below dependency in build.gradle.

    // add exo player dependency here
    implementation 'com.google.android.exoplayer:exoplayer:2.7.3'
    implementation 'org.jsoup:jsoup:1.10.3'

    // code generator for view
    implementation "com.jakewharton:butterknife:8.8.1"
    annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1"

In the gradle.xml file, We have 2 major dependencies. One for ExoPlayer and second as know about ButterKnife already. Basically, the butterknife is injected of view in the java file.

3. Open activity_main.xml and add two Button like below code.

While creating project Android Studio is provide default template like EmptyActivity. So MainActivity.java and activity_main.xml is automatically created. So open activity_main.xml and add to button

<?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"
    android:background="@drawable/bg_design"
    tools:context=".MainActivity"
    >

  <Button
      android:id="@+id/buttonPlayUrlVideo"
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="16dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:background="@color/colorAccent"
      android:text="@string/title_play_url"
      android:textColor="@color/white"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0.5"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/buttonPlayDefaultVideo"
      />

  <Button
      android:id="@+id/buttonPlayDefaultVideo"
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:background="@color/colorAccent"
      android:text="@string/title_play_default_url"
      android:textColor="@color/white"
      app:layout_constraintBottom_toTopOf="@+id/buttonPlayUrlVideo"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0.5"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintVertical_chainStyle="packed"
      />
</android.support.constraint.ConstraintLayout>

As per design, we have created two buttons one for play default video. The second user can put own URL in dialog prompt.

4.1 Now connect this layout with MainActivity.java

Open the main activity, we generate view inject code by using ButterKnief injection and set on click listeners.

  @BindView(R.id.buttonPlayUrlVideo)
  Button buttonPlayUrlVideo;
  @BindView(R.id.buttonPlayDefaultVideo)
  Button buttonPlayDefaultVideo;

  @OnClick({ R.id.buttonPlayUrlVideo, R.id.buttonPlayDefaultVideo })
  public void onViewClicked(View view) {
    switch (view.getId()) {
      case R.id.buttonPlayUrlVideo:
        showDialogPrompt();
        break;
      case R.id.buttonPlayDefaultVideo:
        Intent mIntent =
            ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);
        startActivity(mIntent);
        break;
    }
  }
4.2. Let’s bind butter knife in onCreate Activity
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getSupportActionBar().hide();
    ButterKnife.bind(this);
  }

4.3. -As per above, We are using two buttons. So If you want to play a default video then we call intent with default URL of ExoPlayer activity.
like Below

  // End Activity Here
  Intent mIntent = ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);

  startActivity(mIntent);


4.3. – Button for playing its own video by entering video URL. So taking user URL we have created alert dialog with edit text.

Just create a layout file for dialog view. In res, folder create file named dialog_prompts.xml and add few components

<?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="wrap_content"
    android:padding="@dimen/default_item_padding"
    >

  <TextView
      android:id="@+id/textView1"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:text="Type Your Url"
      android:textAppearance="?android:attr/textAppearanceLarge"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

  <EditText
      android:id="@+id/editTextDialogUrlInput"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/textView1"
      >

    <requestFocus />

  </EditText>

</android.support.constraint.ConstraintLayout>

Open again MainActivity.java and put this code for creating dialog

  private void showDialogPrompt() {
    // get dialog_prompts.xml view
    LayoutInflater li = LayoutInflater.from(this);
    View promptsView = li.inflate(R.layout.dialog_prompts, null);
    AlertDialog.Builder mBuilder = new AlertDialog.Builder( this);
    // set dialog_prompts.xml to dialog
    mBuilder.setView(promptsView);
    final EditText userInputURL = (EditText) promptsView
        .findViewById(R.id.editTextDialogUrlInput);
    // set dialog message here
    mBuilder.setCancelable(false)
        .setPositiveButton("OK",
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int id) {
                boolean isURL = Patterns.WEB_URL.matcher(userInputURL.getText().toString().trim()).matches();
                if (isURL) {
                  Intent mIntent = ExoPlayerActivity.getStartIntent(MainActivity.this, userInputURL.getText().toString().trim());
                  startActivity(mIntent);
                } else {
                  Toast.makeText(MainActivity.this, getString(R.string.error_message_url_not_valid), Toast.LENGTH_SHORT).show();
                }
              }
            })
        .setNegativeButton("Cancel",
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
              }
            }).create().show();
  }

5. Now MainActivity.java full code seems like below

package com.androidwave.exoplayer;

import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

  public class MainActivity extends AppCompatActivity {

    @BindView(R.id.buttonPlayUrlVideo)
    Button buttonPlayUrlVideo;
    @BindView(R.id.buttonPlayDefaultVideo)
    Button buttonPlayDefaultVideo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      getSupportActionBar().hide();
      ButterKnife.bind(this);
    }

    @OnClick({ R.id.buttonPlayUrlVideo, R.id.buttonPlayDefaultVideo })
    public void onViewClicked(View view) {
      switch (view.getId()) {
        case R.id.buttonPlayUrlVideo:
          showDialogPrompt();
          break;
        case R.id.buttonPlayDefaultVideo:
          Intent mIntent =
              ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);
          startActivity(mIntent);
          break;
      }
    }

    private void showDialogPrompt() {
      // get dialog_prompts.xml view
      LayoutInflater li = LayoutInflater.from(this);
      View promptsView = li.inflate(R.layout.dialog_prompts, null);
      AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
      // set dialog_prompts.xml to dialog
      mBuilder.setView(promptsView);
      final EditText userInputURL = (EditText) promptsView
          .findViewById(R.id.editTextDialogUrlInput);
      // set dialog message here
      mBuilder.setCancelable(false)
          .setPositiveButton("OK",
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                  boolean isURL =
                      Patterns.WEB_URL.matcher(userInputURL.getText().toString().trim()).matches();
                  if (isURL) {
                    Intent mIntent = ExoPlayerActivity.getStartIntent(MainActivity.this,
                        userInputURL.getText().toString().trim());
                    startActivity(mIntent);
                  } else {
                    Toast.makeText(MainActivity.this,
                        getString(R.string.error_message_url_not_valid), Toast.LENGTH_SHORT).show();
                  }
                }
              })
          .setNegativeButton("Cancel",
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                  dialog.cancel();
                }
              }).create().show();
    }
  }

6. Configure ExoPlayer

Before going to Exo Player activity we define followings configuration of ExoPlayer.
MIN_BUFFER_DURATION – Minimum Video you want to buffer while Playing. for now, I have set 3 second
MAX_BUFFER_DURATION – You can set Max Video you want to buffer during PlayBack.
MIN_PLAYBACK_START_BUFFER – Set Min Video you want to buffer before start Playing it
MIN_PLAYBACK_RESUME_BUFFER – Set Min video You want to buffer when the user resumes video
DEFAULT_VIDEO_URL – Set Default video url for demo

package com.androidwave.exoplayer;

  public class VideoPlayerConfig {
    //Minimum Video you want to buffer while Playing
    public static final int MIN_BUFFER_DURATION = 3000;
    //Max Video you want to buffer during PlayBack
    public static final int MAX_BUFFER_DURATION = 5000;
    //Min Video you want to buffer before start Playing it
    public static final int MIN_PLAYBACK_START_BUFFER = 1500;
    //Min video You want to buffer when user resumes video
    public static final int MIN_PLAYBACK_RESUME_BUFFER = 5000;

    public static final String DEFAULT_VIDEO_URL =
        "https://androidwave.com/media/androidwave-video-exo-player.mp4";
  }

7. Create a layout file name with activity_exo_player.xml

Create layout file inside res-> layout-> activity_exo_player.xml. and some components Progressbar(buffering indicator) and ExoPlayerView

<?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=".ExoPlayerActivity"
    >

  <com.google.android.exoplayer2.ui.PlayerView
      android:id="@+id/videoFullScreenPlayer"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:background="#A6000000"
      app:controller_layout_id="@layout/exo_playback_control_view"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="1.0"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintVertical_bias="1.0"
      app:player_layout_id="@layout/exo_simple_player_view"
      app:repeat_toggle_modes="none"
      app:show_timeout="45000"
      app:surface_type="texture_view"
      />

  <ProgressBar
      android:id="@+id/spinnerVideoDetails"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:indeterminate="true"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

  <ImageView
      android:id="@+id/imageViewExit"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginLeft="16dp"
      android:layout_marginTop="16dp"
      android:padding="@dimen/default_item_padding"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:srcCompat="@drawable/ic_cross_circular_button_outline"
      />
</android.support.constraint.ConstraintLayout>

8. Same as Create ExoPlayerActivity.java

Go to create an Activity with ExoPlayerActivity.java and set content layout is activity_exo_player.xml. Furthermore generates ButteKnief Injection by select set ContentView and right click and click generate. Now one popup appeared select view and set ids and click ok.

  @BindView(R.id.videoFullScreenPlayer)
  PlayerView videoFullScreenPlayer;
  @BindView(R.id.spinnerVideoDetails)
  ProgressBar spinnerVideoDetails;
  @BindView(R.id.imageViewExit)
  ImageView imageViewExit;
8.1. Define some variables and do some fullscreen configuration
  private static final String TAG = "ExoPlayerActivity";
  private static final String KEY_VIDEO_URI = "video_uri";
  String videoUri;
  SimpleExoPlayer player;
  Handler mHandler;
  Runnable mRunnable;
8.2. We are using vector icon so enable
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
8.3. Now initialise video player configuration
  private void initializePlayer() {
    if (player == null) {
      // 1. Create a default TrackSelector
      LoadControl loadControl = new DefaultLoadControl(
          new DefaultAllocator(true, 16),
          VideoPlayerConfig.MIN_BUFFER_DURATION,
          VideoPlayerConfig.MAX_BUFFER_DURATION,
          VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER,
          VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER, -1, true);

      BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
      TrackSelection.Factory videoTrackSelectionFactory =
          new AdaptiveTrackSelection.Factory(bandwidthMeter);
      TrackSelector trackSelector =
          new DefaultTrackSelector(videoTrackSelectionFactory);
      // 2. Create the player
      player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), trackSelector,
          loadControl);
      videoFullScreenPlayer.setPlayer(player);
    }
  }
8.3. After inilizaing player create a media source using below methods
  private void buildMediaSource(Uri mUri) {
    // Measures bandwidth during playback. Can be null if not required.
    DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    // Produces DataSource instances through which media data is loaded.
    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
        Util.getUserAgent(this, getString(R.string.app_name)), bandwidthMeter);
    // This is the MediaSource representing the media to be played.
    MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
        .createMediaSource(mUri);
    // Prepare the player with the source.
    player.prepare(videoSource);
    player.setPlayWhenReady(true);
    player.addListener(this);
  }
8.4. Implement Player.EventListener and override unimplemented methods

Just implement Player.EventListener you have to implement override methods. onPlayerStateChanged() methods will return all player state.

  @Override
  public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
    switch (playbackState) {

      case Player.STATE_BUFFERING:
        spinnerVideoDetails.setVisibility(View.VISIBLE);
        break;
      case Player.STATE_ENDED:
        // Activate the force enable
        break;
      case Player.STATE_IDLE:

        break;
      case Player.STATE_READY:
        spinnerVideoDetails.setVisibility(View.GONE);

        break;
      default:
        // status = PlaybackStatus.IDLE;
        break;
    }
  }

9. Finally, The full activity (ExoPlayerActivity.java) code looks like this

Mainly we are doing below things in this activity

  • Initialize Player with configuration .
  • Prepare source files and settings on the player.
  • Add Player Event listener for listening to different player state
  • Manage player state with activity life cycler eg. pausePlayer(), resumePlayer(), releasePlayer()
package com.androidwave.exoplayer;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
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.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
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.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

  public class ExoPlayerActivity extends AppCompatActivity implements Player.EventListener {
    private static final String TAG = "ExoPlayerActivity";

    private static final String KEY_VIDEO_URI = "video_uri";

    @BindView(R.id.videoFullScreenPlayer)
    PlayerView videoFullScreenPlayer;
    @BindView(R.id.spinnerVideoDetails)
    ProgressBar spinnerVideoDetails;
    @BindView(R.id.imageViewExit)
    ImageView imageViewExit;

    String videoUri;
    SimpleExoPlayer player;
    Handler mHandler;
    Runnable mRunnable;

    public static Intent getStartIntent(Context context, String videoUri) {
      Intent intent = new Intent(context, ExoPlayerActivity.class);
      intent.putExtra(KEY_VIDEO_URI, videoUri);
      return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      this.getWindow()
          .setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
              WindowManager.LayoutParams.FLAG_FULLSCREEN);
      setContentView(R.layout.activity_exo_player);
      ButterKnife.bind(this);
      AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
      getSupportActionBar().hide();

      if (getIntent().hasExtra(KEY_VIDEO_URI)) {
        videoUri = getIntent().getStringExtra(KEY_VIDEO_URI);
      }

      setUp();
    }

    private void setUp() {
      initializePlayer();
      if (videoUri == null) {
        return;
      }
      buildMediaSource(Uri.parse(videoUri));
    }

    @OnClick(R.id.imageViewExit)
    public void onViewClicked() {
      finish();
    }

    private void initializePlayer() {
      if (player == null) {
        // 1. Create a default TrackSelector
        LoadControl loadControl = new DefaultLoadControl(
            new DefaultAllocator(true, 16),
            VideoPlayerConfig.MIN_BUFFER_DURATION,
            VideoPlayerConfig.MAX_BUFFER_DURATION,
            VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER,
            VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER, -1, true);

        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        TrackSelection.Factory videoTrackSelectionFactory =
            new AdaptiveTrackSelection.Factory(bandwidthMeter);
        TrackSelector trackSelector =
            new DefaultTrackSelector(videoTrackSelectionFactory);
        // 2. Create the player
        player =
            ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), trackSelector,
                loadControl);
        videoFullScreenPlayer.setPlayer(player);
      }
    }

    private void buildMediaSource(Uri mUri) {
      // Measures bandwidth during playback. Can be null if not required.
      DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
      // Produces DataSource instances through which media data is loaded.
      DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
          Util.getUserAgent(this, getString(R.string.app_name)), bandwidthMeter);
      // This is the MediaSource representing the media to be played.
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(mUri);
      // Prepare the player with the source.
      player.prepare(videoSource);
      player.setPlayWhenReady(true);
      player.addListener(this);
    }

    private void releasePlayer() {
      if (player != null) {
        player.release();
        player = null;
      }
    }

    private void pausePlayer() {
      if (player != null) {
        player.setPlayWhenReady(false);
        player.getPlaybackState();
      }
    }

    private void resumePlayer() {
      if (player != null) {
        player.setPlayWhenReady(true);
        player.getPlaybackState();
      }
    }

    @Override
    protected void onPause() {
      super.onPause();
      pausePlayer();
      if (mRunnable != null) {
        mHandler.removeCallbacks(mRunnable);
      }
    }

    @Override
    protected void onRestart() {
      super.onRestart();
      resumePlayer();
    }

    @Override
    protected void onDestroy() {
      super.onDestroy();
      releasePlayer();
    }

    @Override
    public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {

    }

    @Override
    public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

    }

    @Override
    public void onLoadingChanged(boolean isLoading) {

    }

    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
      switch (playbackState) {

        case Player.STATE_BUFFERING:
          spinnerVideoDetails.setVisibility(View.VISIBLE);
          break;
        case Player.STATE_ENDED:
          // Activate the force enable
          break;
        case Player.STATE_IDLE:

          break;
        case Player.STATE_READY:
          spinnerVideoDetails.setVisibility(View.GONE);

          break;
        default:
          // status = PlaybackStatus.IDLE;
          break;
      }
    }

    @Override
    public void onRepeatModeChanged(int repeatMode) {

    }

    @Override
    public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

    }

    @Override
    public void onPlayerError(ExoPlaybackException error) {

    }

    @Override
    public void onPositionDiscontinuity(int reason) {

    }

    @Override
    public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

    }

    @Override
    public void onSeekProcessed() {

    }
  }

Conclusion

Wow! Great job! You just finished the ExoPlayer android example. You learned about integrating ExoPlayer in your application.

Download Sample Project- Video Streaming ExoPlayer in Android

If you have any queries, feel free to connect us.

In the previous tutorial, we demonstrate video streaming over the Internet using ExoPlayer. Somehow you missed previous articles you must read this ExoPlayer in Android for better understanding. In another article we saw how to play audio, video from SD card (local) as well, So you get an idea about ExoPlayer core functionality, DASH and UI library module.

I hope you read my previous tutorials(I can assume you have basic knowledge about ExoPlayer). In this tutorial, I will describe how to implement video playback in the RecyclerView Android. Just like some popular application eg. Magisto, Facebook, Twitter, Instagram.

This article based on ExoPlayer. In case some things missing in this article download full source and a working sample app is there.

Before starting code, Think about the problem statement. Basically, The following major challenges for us to implementing ExoPlayer in RecyclerView.

  • The first problem is managing ExoPlayer playback in RecyclerView
  • Second is we need to know which view on the scrolled list is currently active. Other words you can say which list item is visible 100%. So we can attach the player with a particular view.

Step to implement ExoPlayer in RecyclerView

  • Create Project and set up dependencies
  • Write a custom RecyclerView that are capable of manage video playback.
  • Write an adapter for holding list item
    • Create a model class with getter setter
    • Prepare an XML layout for media list item
    • Write ViewHolder class for representing media list item
    • Complete the RecyclerView Adapter
  • Do followings operation in MainActivity
    • In MainActivity layout, file adds custom ExoRecyclerView that create earlier.
    • Prepare demo content
    • Set the adapter to RecyclerView
    • Run the project

1. Create Project and set up dependencies

All conceptual part is done, let’s move to AndroidStudio create a new project with the default template. Follows few steps your project will be created, now we need to add some dependencies in app/gradle file.

Following dependencies are in this android app tutorial. I hope you already know the uses of use one.

  • RecyclerView – For using RecyclerView
  • ExoPlayer – Using ExoPlayer
  • Glide – For loading media thumbnails
  • Constraint Layout – Performace is much better to other layouts
1.1 Open the app/build.gradle file and add all above dependencies
  // RecyclerView
  implementation 'com.android.support:recyclerview-v7:28.0.0'

  // ExoPlayer
  implementation 'com.google.android.exoplayer:exoplayer:2.7.3'
  // Image loading
  implementation 'com.github.bumptech.glide:glide:4.9.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
1.2 Enable vector drawable by adding below line inside defaultConfig bracket
  vectorDrawables.useSupportLibrary = true

2. Write a custom RecyclerView that is capable manage video playback.

Doing that we have to create a new subclass of RecyclerView named is ExoPlayerRecyclerView. Mean create a new class ExoPlayerRecyclerView.java which extends RecyclerView. In this class, we have to manage two things. First, manage ExoPlayer playback. Second is to find that list item that visibility 100%. So that we will add a player on that particular view.

2.1 Problem – how to manage ExoPlayer playback?
  • Create a class ExoPlayerRecyclerView.java which extend RecyclerView 
  • Get start position and end position of view from LayoutManager after that calculate currently visible view position.
  • Here now on target position, we will add ExoPlayerView and set ExoPlayer.
  //play the video in the row
  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  andwidthMeter =new

  DefaultBandwidthMeter();
  // Produces DataSource instances through which media data is loaded.

  DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(appContext,
      Util.getUserAgent(appContext, "android_wave_list"), defaultBandwidthMeter);
  // This is the MediaSource representing the media to be played.
  String uriString = videoInfoList.get(targetPosition).getUrl();
        if(uriString !=null)

  {
    MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
        .createMediaSource(Uri.parse(uriString));
    // Prepare the player with the source.
    player.prepare(videoSource);
    player.setPlayWhenReady(true);
  }
}
2.2 Second Problem – Get visible video surface scrolled list.
 /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

I have solved both problems, The complete code of ExoPlayerRecyclerView is

package com.androidwave.exoplayer.ui;

import android.content.Context;
import android.graphics.Point;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.adapter.PlayerViewHolder;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
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.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
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.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Objects;

/**
 * Created on : May 24, 2019
 * Author     : AndroidWave
 */
public class ExoPlayerRecyclerView extends RecyclerView {

  private static final String TAG = "ExoPlayerRecyclerView";
  private static final String AppName = "Android ExoPlayer";
  /**
   * PlayerViewHolder UI component
   * Watch PlayerViewHolder class
   */
  private ImageView mediaCoverImage, volumeControl;
  private ProgressBar progressBar;
  private View viewHolderParent;
  private FrameLayout mediaContainer;
  private PlayerView videoSurfaceView;
  private SimpleExoPlayer videoPlayer;
  /**
   * variable declaration
   */
  // Media List
  private ArrayList<MediaObject> mediaObjects = new ArrayList<>();
  private int videoSurfaceDefaultHeight = 0;
  private int screenDefaultHeight = 0;
  private Context context;
  private int playPosition = -1;
  private boolean isVideoViewAdded;
  private RequestManager requestManager;
  // controlling volume state
  private VolumeState volumeState;
  private OnClickListener videoViewClickListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
      toggleVolume();
    }
  };

  public ExoPlayerRecyclerView(@NonNull Context context) {
    super(context);
    init(context);
  }

  public ExoPlayerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  private void init(Context context) {
    this.context = context.getApplicationContext();
    Display display = ((WindowManager) Objects.requireNonNull(
        getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay();
    Point point = new Point();
    display.getSize(point);

    videoSurfaceDefaultHeight = point.x;
    screenDefaultHeight = point.y;

    videoSurfaceView = new PlayerView(this.context);
    videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);

    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    TrackSelection.Factory videoTrackSelectionFactory =
        new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector =
        new DefaultTrackSelector(videoTrackSelectionFactory);

    //Create the player using ExoPlayerFactory
    videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
    // Disable Player Control
    videoSurfaceView.setUseController(false);
    // Bind the player to the view.
    videoSurfaceView.setPlayer(videoPlayer);
    // Turn on Volume
    setVolumeControl(VolumeState.ON);

    addOnScrollListener(new RecyclerView.OnScrollListener() {
      @Override
      public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
          if (mediaCoverImage != null) {
            // show the old thumbnail
            mediaCoverImage.setVisibility(VISIBLE);
          }

          // There's a special case when the end of the list has been reached.
          // Need to handle that with this bit of logic
          if (!recyclerView.canScrollVertically(1)) {
            playVideo(true);
          } else {
            playVideo(false);
          }
        }
      }

      @Override
      public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
      }
    });

    addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
      @Override
      public void onChildViewAttachedToWindow(@NonNull View view) {

      }

      @Override
      public void onChildViewDetachedFromWindow(@NonNull View view) {
        if (viewHolderParent != null && viewHolderParent.equals(view)) {
          resetVideoView();
        }
      }
    });

    videoPlayer.addListener(new Player.EventListener() {
      @Override
      public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {

      }

      @Override
      public void onTracksChanged(TrackGroupArray trackGroups,
          TrackSelectionArray trackSelections) {

      }

      @Override
      public void onLoadingChanged(boolean isLoading) {

      }

      @Override
      public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        switch (playbackState) {

          case Player.STATE_BUFFERING:
            Log.e(TAG, "onPlayerStateChanged: Buffering video.");
            if (progressBar != null) {
              progressBar.setVisibility(VISIBLE);
            }

            break;
          case Player.STATE_ENDED:
            Log.d(TAG, "onPlayerStateChanged: Video ended.");
            videoPlayer.seekTo(0);
            break;
          case Player.STATE_IDLE:

            break;
          case Player.STATE_READY:
            Log.e(TAG, "onPlayerStateChanged: Ready to play.");
            if (progressBar != null) {
              progressBar.setVisibility(GONE);
            }
            if (!isVideoViewAdded) {
              addVideoView();
            }
            break;
          default:
            break;
        }
      }

      @Override
      public void onRepeatModeChanged(int repeatMode) {

      }

      @Override
      public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

      }

      @Override
      public void onPlayerError(ExoPlaybackException error) {

      }

      @Override
      public void onPositionDiscontinuity(int reason) {

      }

      @Override
      public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

      }

      @Override
      public void onSeekProcessed() {

      }
    });
  }

  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

  // Remove the old player
  private void removeVideoView(PlayerView videoView) {
    ViewGroup parent = (ViewGroup) videoView.getParent();
    if (parent == null) {
      return;
    }

    int index = parent.indexOfChild(videoView);
    if (index >= 0) {
      parent.removeViewAt(index);
      isVideoViewAdded = false;
      viewHolderParent.setOnClickListener(null);
    }
  }

  private void addVideoView() {
    mediaContainer.addView(videoSurfaceView);
    isVideoViewAdded = true;
    videoSurfaceView.requestFocus();
    videoSurfaceView.setVisibility(VISIBLE);
    videoSurfaceView.setAlpha(1);
    mediaCoverImage.setVisibility(GONE);
  }

  private void resetVideoView() {
    if (isVideoViewAdded) {
      removeVideoView(videoSurfaceView);
      playPosition = -1;
      videoSurfaceView.setVisibility(INVISIBLE);
      mediaCoverImage.setVisibility(VISIBLE);
    }
  }

  public void releasePlayer() {

    if (videoPlayer != null) {
      videoPlayer.release();
      videoPlayer = null;
    }

    viewHolderParent = null;
  }

  public void onPausePlayer() {
    if (videoPlayer != null) {
      videoPlayer.stop(true);
    }
  }

  private void toggleVolume() {
    if (videoPlayer != null) {
      if (volumeState == VolumeState.OFF) {
        Log.d(TAG, "togglePlaybackState: enabling volume.");
        setVolumeControl(VolumeState.ON);
      } else if (volumeState == VolumeState.ON) {
        Log.d(TAG, "togglePlaybackState: disabling volume.");
        setVolumeControl(VolumeState.OFF);
      }
    }
  }

  private void setVolumeControl(VolumeState state) {
    volumeState = state;
    if (state == VolumeState.OFF) {
      videoPlayer.setVolume(0f);
      animateVolumeControl();
    } else if (state == VolumeState.ON) {
      videoPlayer.setVolume(1f);
      animateVolumeControl();
    }
  }

  private void animateVolumeControl() {
    if (volumeControl != null) {
      volumeControl.bringToFront();
      if (volumeState == VolumeState.OFF) {
        requestManager.load(R.drawable.ic_volume_off)
            .into(volumeControl);
      } else if (volumeState == VolumeState.ON) {
        requestManager.load(R.drawable.ic_volume_on)
            .into(volumeControl);
      }
      volumeControl.animate().cancel();

      volumeControl.setAlpha(1f);

      volumeControl.animate()
          .alpha(0f)
          .setDuration(600).setStartDelay(1000);
    }
  }

  public void setMediaObjects(ArrayList<MediaObject> mediaObjects) {
    this.mediaObjects = mediaObjects;
  }

  /**
   * Volume ENUM
   */
  private enum VolumeState {
    ON, OFF
  }
}

Now ExoPlayerRecyclerView.java component is ready to use. Now I will explain how to use this component.

3. Write an adapter for holding list item

Before moving ahead we need to write some helper class for RecyclerView adapter,

3.1 Create a model class with getter setter

Go to the src folder and create the model folder for separating all model classes. Inside this folder create a POJO class named MediaObject.java. That has getter and setter of title and cover image URL and media URL

package com.androidwave.exoplayer.model;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaObject {
  private int uId;
  private String title;
  private String mediaUrl;
  private String mediaCoverImgUrl;
  private String userHandle;

  public String getUserHandle() {
    return userHandle;
  }

  public void setUserHandle(String mUserHandle) {
    this.userHandle = mUserHandle;
  }

  public int getId() {
    return uId;
  }

  public void setId(int uId) {
    this.uId = uId;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String mTitle) {
    this.title = mTitle;
  }

  public String getUrl() {
    return mediaUrl;
  }

  public void setUrl(String mUrl) {
    this.mediaUrl = mUrl;
  }

  public String getCoverUrl() {
    return mediaCoverImgUrl;
  }

  public void setCoverUrl(String mCoverUrl) {
    this.mediaCoverImgUrl = mCoverUrl;
  }
}
3.2 Prepare an XML layout for media list item

Create a layout file inside the res => layout folder named is layout_media_list_item.xml, which holds the view of the list item. So add some view component in this layout file.

<?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="wrap_content"
    android:layout_margin="8dp"
    android:background="@color/white"
    >

  <TextView
      android:id="@+id/tvTitle"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:letterSpacing="-0.02"
      android:lineSpacingExtra="5sp"
      android:textColor="@color/navy"
      android:textSize="18sp"
      android:textStyle="bold"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      tools:text="Was india better then south africa in current serise?"
      />

  <ImageView
      android:id="@+id/imageView"
      android:layout_width="25dp"
      android:layout_height="25dp"
      android:layout_marginBottom="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      app:layout_constraintBottom_toTopOf="@+id/mediaContainer"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/tvTitle"
      app:srcCompat="@drawable/ic_user"
      />

  <TextView
      android:id="@+id/tvUserHandle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:letterSpacing="-0.02"
      android:textColor="@color/windows_blue"
      android:textSize="10sp"
      app:layout_constraintBottom_toBottomOf="@+id/imageView"
      app:layout_constraintStart_toEndOf="@+id/imageView"
      app:layout_constraintTop_toTopOf="@+id/imageView"
      tools:text="by @morris12"
      />

  <FrameLayout
      android:id="@+id/mediaContainer"
      android:layout_width="match_parent"
      android:layout_height="300dp"
      android:layout_marginBottom="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:adjustViewBounds="true"
      android:background="@android:color/black"
      android:gravity="center"
      android:scaleType="center"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/imageView"
      >

    <ImageView
        android:id="@+id/ivMediaCoverImage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scaleType="centerCrop"
        android:src="@drawable/cover"
        />


    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"
        style="?android:attr/progressBarStyle"
        />
    <ImageView
        android:id="@+id/ivVolumeControl"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_gravity="end|bottom"
        android:layout_marginBottom="15dp"
        android:layout_marginEnd="15dp"
        android:alpha="0"
        android:animateLayoutChanges="true"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_volume_on"
        />
  </FrameLayout>

</android.support.constraint.ConstraintLayout>
3.4 Write ViewHolder class for representing media list item

Create a new class inside the adapter package named is PlayerViewHolder. This class will extends RecyclerView.ViewHolder. Initialize the all list item inside this view.

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;

/**
 * Created by Morris on 03,June,2019
 */
public class PlayerViewHolder extends RecyclerView.ViewHolder {

  /**
   * below view have public modifier because
   * we have access PlayerViewHolder inside the ExoPlayerRecyclerView
   */
  public FrameLayout mediaContainer;
  public ImageView mediaCoverImage, volumeControl;
  public ProgressBar progressBar;
  public RequestManager requestManager;
  private TextView title, userHandle;
  private View parent;

  public PlayerViewHolder(@NonNull View itemView) {
    super(itemView);
    parent = itemView;
    mediaContainer = itemView.findViewById(R.id.mediaContainer);
    mediaCoverImage = itemView.findViewById(R.id.ivMediaCoverImage);
    title = itemView.findViewById(R.id.tvTitle);
    userHandle = itemView.findViewById(R.id.tvUserHandle);
    progressBar = itemView.findViewById(R.id.progressBar);
    volumeControl = itemView.findViewById(R.id.ivVolumeControl);
  }

  void onBind(MediaObject mediaObject, RequestManager requestManager) {
    this.requestManager = requestManager;
    parent.setTag(this);
    title.setText(mediaObject.getTitle());
    userHandle.setText(mediaObject.getUserHandle());
    this.requestManager
        .load(mediaObject.getCoverUrl())
        .into(mediaCoverImage);
  }
}
3.5 Complete the RecyclerView Adapter

Doing that create an adapter class named is MediaRecyclerAdapter and paste below code

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  private ArrayList<MediaObject> mediaObjects;
  private RequestManager requestManager;

  public MediaRecyclerAdapter(ArrayList<MediaObject> mediaObjects,
      RequestManager requestManager) {
    this.mediaObjects = mediaObjects;
    this.requestManager = requestManager;
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new PlayerViewHolder(
        LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.layout_media_list_item, viewGroup, false));
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
    ((PlayerViewHolder) viewHolder).onBind(mediaObjects.get(i), requestManager);
  }

  @Override
  public int getItemCount() {
    return mediaObjects.size();
  }
}

4. Do followings operation in MainActivity

All part is done, Now come to activity part.

4.1 Lets add ExoPlayerRecyclerView inside the XML file

If you selected some default template while creating a project, MainActivity.java and activity_main.xml class will automatically be created. Open the activity_main and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    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"
    tools:context=".MainActivity"
    >

  <com.androidwave.exoplayer.ui.ExoPlayerRecyclerView
      android:id="@+id/exoPlayerRecyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#dedede"
      android:dividerHeight="8dp"
      />

</android.support.constraint.ConstraintLayout>
4.2 Prepare demo content

For displaying data on RecyclerView We have to prepare a demo content.

 private void prepareVideoList() {
    VideoInfo videoInfo = new VideoInfo();
    videoInfo.setId(1);
    videoInfo.setUserHandle("@h.pandya");
    videoInfo.setTitle("Do you think the concept of marriage will no longer exist in the future?");
    videoInfo.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    videoInfo.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    VideoInfo videoInfo2 = new VideoInfo();
    videoInfo2.setId(2);
    videoInfo2.setUserHandle("@hardik.patel");
    videoInfo2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    videoInfo2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    videoInfo2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    VideoInfo videoInfo3 = new VideoInfo();
    videoInfo3.setId(3);
    videoInfo3.setUserHandle("@arun.gandhi");
    videoInfo3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    videoInfo3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    videoInfo3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    VideoInfo videoInfo4 = new VideoInfo();
    videoInfo4.setId(4);
    videoInfo4.setUserHandle("@sachin.patel");
    videoInfo4.setTitle("When did kama founders find sex offensive to Indian traditions");
    videoInfo4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    videoInfo4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    VideoInfo videoInfo5 = new VideoInfo();
    videoInfo5.setId(5);
    videoInfo5.setUserHandle("@monika.sharma");
    videoInfo5.setTitle("When did you last cry in front of someone?");
    videoInfo5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    videoInfo5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
  }

4.3 Finally MainActivity source code looks like

package com.androidwave.exoplayer;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import com.androidwave.exoplayer.adapter.MediaRecyclerAdapter;
import com.androidwave.exoplayer.model.MediaObject;
import com.androidwave.exoplayer.ui.ExoPlayerRecyclerView;
import com.androidwave.exoplayer.utils.DividerItemDecoration;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;

import static android.widget.LinearLayout.VERTICAL;

/**
 * Created by Morris on 03,June,2019
 */
public class MainActivity extends AppCompatActivity {

  ExoPlayerRecyclerView mRecyclerView;

  private ArrayList<MediaObject> mediaObjectList = new ArrayList<>();
  private MediaRecyclerAdapter mAdapter;
  private boolean firstTime = true;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    // Prepare demo content
    prepareVideoList();

    //set data object
    mRecyclerView.setMediaObjects(mediaObjectList);
    mAdapter = new MediaRecyclerAdapter(mediaObjectList, initGlide());

    //Set Adapter
    mRecyclerView.setAdapter(mAdapter);

    if (firstTime) {
      new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
          mRecyclerView.playVideo(false);
        }
      });
      firstTime = false;
    }
  }

  private void initView() {
    mRecyclerView = findViewById(R.id.exoPlayerRecyclerView);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this, VERTICAL, false));
    Drawable dividerDrawable = ContextCompat.getDrawable(this, R.drawable.divider_drawable);
    mRecyclerView.addItemDecoration(new DividerItemDecoration(dividerDrawable));
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
  }

  private RequestManager initGlide() {
    RequestOptions options = new RequestOptions();

    return Glide.with(this)
        .setDefaultRequestOptions(options);
  }

  @Override
  protected void onDestroy() {
    if (mRecyclerView != null) {
      mRecyclerView.releasePlayer();
    }
    super.onDestroy();
  }

  private void prepareVideoList() {
    MediaObject mediaObject = new MediaObject();
    mediaObject.setId(1);
    mediaObject.setUserHandle("@h.pandya");
    mediaObject.setTitle(
        "Do you think the concept of marriage will no longer exist in the future?");
    mediaObject.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    mediaObject.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    MediaObject mediaObject2 = new MediaObject();
    mediaObject2.setId(2);
    mediaObject2.setUserHandle("@hardik.patel");
    mediaObject2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    mediaObject2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    mediaObject2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    MediaObject mediaObject3 = new MediaObject();
    mediaObject3.setId(3);
    mediaObject3.setUserHandle("@arun.gandhi");
    mediaObject3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    mediaObject3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    mediaObject3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    MediaObject mediaObject4 = new MediaObject();
    mediaObject4.setId(4);
    mediaObject4.setUserHandle("@sachin.patel");
    mediaObject4.setTitle("When did kama founders find sex offensive to Indian traditions");
    mediaObject4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    mediaObject4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    MediaObject mediaObject5 = new MediaObject();
    mediaObject5.setId(5);
    mediaObject5.setUserHandle("@monika.sharma");
    mediaObject5.setTitle("When did you last cry in front of someone?");
    mediaObject5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    mediaObject5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
  }
}

Conclusion

You almost finished. That’s all for ExoPlayer in RecyclerViiew. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development. I

Download Sample Project- ExoPlayer in RecyclerView in Android

If you have any queries, feel free to ask them in the comment section below. Happy Coding 🙂

Read more on android architecture components tutorials

Introduction

If you facing any issue in video recording in camera2 APIs in Android. Almost, you are in right place. Camera2 APIs are not working well in few devices like Samsung, Nexus 5, etc.

Mostly the of followings issue has occurred in Camera2 API Android

  • The recording is successfully complete but while you play this file video will freeze in the first frame.
  • Sometimes you feel the video appears to be frozen in playback or recorded video playback video and audio is out of Sync
  • First audio plays completely and after then the video animates after the audio completes. If you look at the video playback controls the video starts when it looks like the playback is at the end of the video

So all the above issue is the same.

Its happen due to audio and video frame out of sync. Now come on solution We need to sync audio and video frame.

1. Prerqusion

If you want to no more about Camera2 APIs, Please go to my previous blog Camera2VideoRecording. This blog i have build play and plug solution for Camera2 APIs.

2. Add mp4parser dependency in build.gradle

dependencies {
    //Video Parser
    implementation 'com.googlecode.mp4parser:isoparser:1.1.22'
}

3. In the previous example in Video Recording in Camera2 API in CameraFragment.java.

While we call startRecordingVideo() Utility methods. The file created in local storage get this file using getCurrentFile() methods.

startRecordingVideo();
//Receive out put file here
 mOutputFilePath = getCurrentFile().getAbsolutePath();

mOutputFilePath is the path out the recorded file. Now fetch audio and video tracks check audio and video delta. if delta is greater then 10000 means audio and video track is out of sync. You are thinking why we will take 10000 here,10000 seems sufficient since for 30 fps the normal delta is about 3000. If the frame is out of sync then merge these track using MP$ Parser lib. The code looks like

private String parseVideo(String mFilePath) throws IOException {
       DataSource channel = new FileDataSourceImpl(mFilePath);
       IsoFile isoFile = new IsoFile(channel);
       List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
       boolean isError = false;
       for (TrackBox trackBox : trackBoxes) {
           TimeToSampleBox.Entry firstEntry = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox().getTimeToSampleBox().getEntries().get(0);
           // Detect if first sample is a problem and fix it in isoFile
           // This is a hack. The audio deltas are 1024 for my files, and video deltas about 3000
           // 10000 seems sufficient since for 30 fps the normal delta is about 3000
           if (firstEntry.getDelta() > 10000) {
               isError = true;
               firstEntry.setDelta(3000);
           }
       }
       File file = getOutputMediaFile();
       String filePath = file.getAbsolutePath();
       if (isError) {
           Movie movie = new Movie();
           for (TrackBox trackBox : trackBoxes) {
               movie.addTrack(new Mp4TrackImpl(channel.toString() + "[" + trackBox.getTrackHeaderBox().getTrackId() + "]", trackBox));
           }
           movie.setMatrix(isoFile.getMovieBox().getMovieHeaderBox().getMatrix());
           Container out = new DefaultMp4Builder().build(movie);
 
           //delete file first!
           FileChannel fc = new RandomAccessFile(filePath, "rw").getChannel();
           out.writeContainer(fc);
           fc.close();
           Log.d(TAG, "Finished correcting raw video");
           return filePath;
       }
       return mFilePath;
   } 

The getOutputMediaFile() method create a new file and return File Object.

**
 * Create directory and return file
 * returning video file
 */
private File getOutputMediaFile() {
    // External sdcard file location
    File mediaStorageDir = new File(Environment.getExternalStorageDirectory(),
            VIDEO_DIRECTORY_NAME);
    // Create storage directory if it does not exist
    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            Log.d(TAG, "Oops! Failed create "
                    + VIDEO_DIRECTORY_NAME + " directory");
            return null;
        }
    }
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
            Locale.getDefault()).format(new Date());
    File mediaFile;
 
    mediaFile = new File(mediaStorageDir.getPath() + File.separator
            + "VID_" + timeStamp + ".mp4");
    return mediaFile;
}

Therefore final fragment looks like this

CameraFragment.java
**
 * A simple {@link Fragment} subclass.
 * Use the {@link CameraFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class CameraFragment extends CameraVideoFragment {
 
    private static final String TAG = "CameraFragment";
    private static final String VIDEO_DIRECTORY_NAME = "AndroidWave";
    @BindView(R.id.mTextureView)
    AutoFitTextureView mTextureView;
    @BindView(R.id.mRecordVideo)
    ImageView mRecordVideo;
    @BindView(R.id.mVideoView)
    VideoView mVideoView;
    @BindView(R.id.mPlayVideo)
    ImageView mPlayVideo;
    Unbinder unbinder;
    private String mOutputFilePath;
 
 
    public CameraFragment() {
        // Required empty public constructor
    }
 
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     */
 
 
    public static CameraFragment newInstance() {
        CameraFragment fragment = new CameraFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_camera, container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }
 
    @Override
    public int getTextureResource() {
        return R.id.mTextureView;
    }
 
    @Override
    protected void setUp(View view) {
 
    }
 
    @OnClick({R.id.mRecordVideo, R.id.mPlayVideo})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.mRecordVideo:
                /**
                 * If media is not recoding then start recording else stop recording
                 */
                if (mIsRecordingVideo) {
                    try {
                        stopRecordingVideo();
                        prepareViews();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
 
                } else {
                    startRecordingVideo();
                    mRecordVideo.setImageResource(R.drawable.ic_stop);
                    //Receive out put file here
                    mOutputFilePath = getCurrentFile().getAbsolutePath();
                }
                break;
            case R.id.mPlayVideo:
                mVideoView.start();
                mPlayVideo.setVisibility(View.GONE);
                break;
        }
    }
 
    private void prepareViews() {
        if (mVideoView.getVisibility() == View.GONE) {
            mVideoView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.VISIBLE);
            mTextureView.setVisibility(View.GONE);
            try {
                setMediaForRecordVideo();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    private void setMediaForRecordVideo() throws IOException {
        mOutputFilePath = parseVideo(mOutputFilePath);
        // Set media controller
        mVideoView.setMediaController(new MediaController(getActivity()));
        mVideoView.requestFocus();
        mVideoView.setVideoPath(mOutputFilePath);
        mVideoView.seekTo(100);
        mVideoView.setOnCompletionListener(mp -> {
            // Reset player
            mVideoView.setVisibility(View.GONE);
            mTextureView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.GONE);
            mRecordVideo.setImageResource(R.drawable.ic_record);
        });
    }
 
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }
 
   <strong> private String parseVideo(String mFilePath) throws IOException {
        DataSource channel = new FileDataSourceImpl(mFilePath);
        IsoFile isoFile = new IsoFile(channel);
        List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
        boolean isError = false;
        for (TrackBox trackBox : trackBoxes) {
            TimeToSampleBox.Entry firstEntry = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox().getTimeToSampleBox().getEntries().get(0);
            // Detect if first sample is a problem and fix it in isoFile
            // This is a hack. The audio deltas are 1024 for my files, and video deltas about 3000
            // 10000 seems sufficient since for 30 fps the normal delta is about 3000
            if (firstEntry.getDelta() > 10000) {
                isError = true;
                firstEntry.setDelta(3000);
            }
        }
        File file = getOutputMediaFile();
        String filePath = file.getAbsolutePath();
        if (isError) {
            Movie movie = new Movie();
            for (TrackBox trackBox : trackBoxes) {
                movie.addTrack(new Mp4TrackImpl(channel.toString() + "[" + trackBox.getTrackHeaderBox().getTrackId() + "]", trackBox));
            }
            movie.setMatrix(isoFile.getMovieBox().getMovieHeaderBox().getMatrix());
            Container out = new DefaultMp4Builder().build(movie);
 
            //delete file first!
            FileChannel fc = new RandomAccessFile(filePath, "rw").getChannel();
            out.writeContainer(fc);
            fc.close();
            Log.d(TAG, "Finished correcting raw video");
            return filePath;
        }
        return mFilePath;
    }</strong>
 
    /**
     * Create directory and return file
     * returning video file
     */
    private File getOutputMediaFile() {
        // External sdcard file location
        File mediaStorageDir = new File(Environment.getExternalStorageDirectory(),
                VIDEO_DIRECTORY_NAME);
        // Create storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d(TAG, "Oops! Failed create "
                        + VIDEO_DIRECTORY_NAME + " directory");
                return null;
            }
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                Locale.getDefault()).format(new Date());
        File mediaFile;
 
        mediaFile = new File(mediaStorageDir.getPath() + File.separator
                + "VID_" + timeStamp + ".mp4");
        return mediaFile;
    }
}
Download Sample Project- Camera2 API Android, Audio Video out of Sync in Issue

Introduction

Camera2 API is an upgraded model of the Camera device. Today you look many apps with rich camera features in markets like Instagram and Snapchat.   In earlier, we used the camera for video and image capture.

In 2014 google introduce Camera2 API with lollipop version (API Version 21). I would like to suggest you must use Camera API 2 if not have version constraint. Camera2 API is not supported below 21 API.

Camera2 device model takes input request to capture a single frame and a single image as per user. Each request is processed in a order, multiple requests can process at a time.

If already integrate camera2 API in your project and you feel the video appears to be frozen in playback, read our another article Audio Video out of Sync in Issue

Prerequisite

  • Create a new Project
  • Ensure is  MinSDK 21
  • Request storage, mic and camera permission
  • Setup Camera2 API

Step-1. Project Setup

Create a new project in android studio from File Menu => New Project => Enter Project Name => Set min SDK 21 => Select EmptyActivity from template.

Step-2. First of all set Min SDK 21

You must set minSdkVersion 21.  Camera2 API is not supported below 21 API.    

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.androidwave.camera2video"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Step-3. Furthermore, add below dependency Permission Request

3.1 Set below dependency for runtime permission request
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'com.android.support:design:27.1.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    // request permission
    implementation 'com.karumi:dexter:4.2.0'
    // ButterKnife Dependency Injection
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
3.2 Declare below permission in AndroidManifest.xml
 <! – declare storage, camera and audio permission -->
 <uses-permission android:name="android.permission.CAMERA"/>
 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.3 Request Camera Permission in runtime in activity
  /**
   * Requesting permissions storage, audio and camera at once
   */
  public void requestPermission() {
    Dexter.withActivity(getActivity())
        .withPermissions(Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
        .withListener(new MultiplePermissionsListener() {
          @Override
          public void onPermissionsChecked(MultiplePermissionsReport report) {
            // check if all permissions are granted or not
            if (report.areAllPermissionsGranted()) {
              if (mTextureView.isAvailable()) {
                openCamera(mTextureView.getWidth(), mTextureView.getHeight());
              } else {
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
              }
            }
            // check for permanent denial of any permission show alert dialog
            if (report.isAnyPermissionPermanentlyDenied()) {
              // open Settings activity
              showSettingsDialog();
            }
          }
          @Override
          public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions,
              PermissionToken token) {
            token.continuePermissionRequest();
          }
        })
        .withErrorListener(
            error -> Toast.makeText(getActivity().getApplicationContext(), "Error occurred! ",
                Toast.LENGTH_SHORT).show())
        .onSameThread()
        .check();
  }
  /**
   * Showing Alert Dialog with Settings option in case of deny any permission
   */
  private void showSettingsDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle(getString(R.string.message_need_permission));
    builder.setMessage(getString(R.string.message_permission));
    builder.setPositiveButton(getString(R.string.title_go_to_setting), (dialog, which) -> {
      dialog.cancel();
      openSettings();
    });
    builder.show();
  }
  // navigating settings app
  private void openSettings() {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
    intent.setData(uri);
    startActivityForResult(intent, 101);
  }

Step-4. Create java file for camera preview name is AutoFitTextureView.java

Create a AutoFitTextureView which extends TextureView. The Java code looks Like this.

package com.androidwave.camera2video.camera;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
  public class AutoFitTextureView extends TextureView {
    private int mRatioWidth = 0;
    private int mRatioHeight = 0;
    public AutoFitTextureView(Context context) {
      this(context, null);
    }
    public AutoFitTextureView(Context context, AttributeSet attrs) {
      this(context, attrs, 0);
    }
    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
    }
    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width Relative horizontal size
     * @param height Relative vertical size
     */
    public void setAspectRatio(int width, int height) {
      if (width < 0 || height < 0) {
        throw new IllegalArgumentException("Size cannot be negative.");
      }
      mRatioWidth = width;
      mRatioHeight = height;
      requestLayout();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int width = MeasureSpec.getSize(widthMeasureSpec);
      int height = MeasureSpec.getSize(heightMeasureSpec);
      if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
      } else {
        if (width < height * mRatioWidth / mRatioHeight) {
          setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
        } else {
          setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
        }
      }
    }
  }

Step-5. Open Camera

Check if all permissions are granted then open camera using below code

  /**
   * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
   */
  private void openCamera(int width, int height) {
    final Activity activity = getActivity();
    if (null == activity || activity.isFinishing()) {
      return;
    }
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
      Log.d(TAG, "tryAcquire");
      if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
        throw new RuntimeException("Time out waiting to lock camera opening.");
      }
      /**
       * default front camera will activate
       */
      String cameraId = manager.getCameraIdList()[0];
      CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
      StreamConfigurationMap map = characteristics
          .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
      mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
      if (map == null) {
        throw new RuntimeException("Cannot get available preview/video sizes");
      }
      mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
      mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
          width, height, mVideoSize);
      int orientation = getResources().getConfiguration().orientation;
      if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
        mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      } else {
        mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
      }
      configureTransform(width, height);
      mMediaRecorder = new MediaRecorder();
      if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
          != PackageManager.PERMISSION_GRANTED) {
        // TODO: Consider calling
        requestPermission();
        return;
      }
      manager.openCamera(cameraId, mStateCallback, null);
    } catch (CameraAccessException e) {
      Log.e(TAG, "openCamera: Cannot access the camera.");
    } catch (NullPointerException e) {
      Log.e(TAG, "Camera2API is not supported on the device.");
    } catch (InterruptedException e) {
      throw new RuntimeException("Interrupted while trying to lock camera opening.");
    }
  }

Step-6. Choose the aspect ratio of video size

Choose the aspect ratio of video size. The video aspect ratio of the video should be 3×4 or 16×9. Also, we don’t use sizes larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.

  /**
   * In this sample, we choose a video size with 3x4 for  aspect ratio. for more perfectness 720 as
   * well Also, we don't use sizes
   * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
   *
   * @param choices The list of available sizes
   * @return The video size 1080p,720px
   */
  private static Size chooseVideoSize(Size[] choices) {
    for (Size size : choices) {
      if (1920 == size.getWidth() && 1080 == size.getHeight()) {
        return size;
      }
    }
    for (Size size : choices) {
      if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
        return size;
      }
    }
    Log.e(TAG, "Couldn't find any suitable video size");
    return choices[choices.length - 1];
  }
  /**
   * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
   * width and height are at least as large as the respective requested values, and whose aspect
   * ratio matches with the specified value.
   *
   * @param choices The list of sizes that the camera supports for the intended output class
   * @param width The minimum desired width
   * @param height The minimum desired height
   * @param aspectRatio The aspect ratio
   * @return The optimal {@code Size}, or an arbitrary one if none were big enough
   */
  private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
    // Collect the supported resolutions that are at least as big as the preview Surface
    List<Size> bigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
      if (option.getHeight() == option.getWidth() * h / w &&
          option.getWidth() >= width && option.getHeight() >= height) {
        bigEnough.add(option);
      }
    }
    // Pick the smallest of those, assuming we found any
    if (bigEnough.size() > 0) {
      return Collections.min(bigEnough, new CompareSizesByArea());
    } else {
      Log.e(TAG, "Couldn't find any suitable preview size");
      return choices[0];
    }
  }

Step-7. Show video previews

After allowing above permission render preview on AutofitTextureView

  /**
   * Start the camera preview.
   */
  private void startPreview() {
    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
      return;
    }
    try {
      closePreviewSession();
      SurfaceTexture texture = mTextureView.getSurfaceTexture();
      assert texture != null;
      texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      Surface previewSurface = new Surface(texture);
      mPreviewBuilder.addTarget(previewSurface);
      mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
          new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
              mPreviewSession = session;
              updatePreview();
            }
            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
              Log.e(TAG, "onConfigureFailed: Failed ");
            }
          }, mBackgroundHandler);
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
  }

Step-8. Setup media recorder

So now you able to preview on screen. If we want to start video recording lets configure the MediaRecorder

  private void setUpMediaRecorder() throws IOException {
    final Activity activity = getActivity();
    if (null == activity) {
      return;
    }
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    /**
     * create video output file
     */
    mCurrentFile = getOutputMediaFile();
    /**
     * set output file in media recorder
     */
    mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath());
    CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
    mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
    mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
    mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
    mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
    mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    switch (mSensorOrientation) {
      case SENSOR_ORIENTATION_DEFAULT_DEGREES:
        mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
        break;
      case SENSOR_ORIENTATION_INVERSE_DEGREES:
        mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
        break;
    }
    mMediaRecorder.prepare();
  }

Step-9. After setting, media lets start Video Recording via below code

public void startRecordingVideo() {
    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
      return;
    }
    try {
      closePreviewSession();
      setUpMediaRecorder();
      SurfaceTexture texture = mTextureView.getSurfaceTexture();
      assert texture != null;
      texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
      List<Surface> surfaces = new ArrayList<>();
      /**
       * Surface for the camera preview set up
       */
      Surface previewSurface = new Surface(texture);
      surfaces.add(previewSurface);
      mPreviewBuilder.addTarget(previewSurface);
      //MediaRecorder setup for surface
      Surface recorderSurface = mMediaRecorder.getSurface();
      surfaces.add(recorderSurface);
      mPreviewBuilder.addTarget(recorderSurface);
      // Start a capture session
      mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
          mPreviewSession = cameraCaptureSession;
          updatePreview();
          getActivity().runOnUiThread(() -> {
            mIsRecordingVideo = true;
            // Start recording
            mMediaRecorder.start();
          });
        }
        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
          Log.e(TAG, "onConfigureFailed: Failed");
        }
      }, mBackgroundHandler);
    } catch (CameraAccessException | IOException e) {
      e.printStackTrace();
    }
  }

Step-10. After that stop video record via below code.

  public void stopRecordingVideo() throws Exception {
    // UI
    mIsRecordingVideo = false;
    try {
      mPreviewSession.stopRepeating();
      mPreviewSession.abortCaptures();
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
    // Stop recording
    mMediaRecorder.stop();
    mMediaRecorder.reset();
  }
All things are done now Now CameraVideoFragment utility is ready to use. Just extend CameraVideoFragment class in place of Fragment. This final preview of CameraVideoFragment.java
package com.androidwave.camera2video.camera;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;
import com.androidwave.camera2video.R;
import com.androidwave.camera2video.ui.base.BaseFragment;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
  public abstract class CameraVideoFragment extends BaseFragment {
    private static final String TAG = "CameraVideoFragment";
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    static {
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
    }
    static {
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
    }
    private File mCurrentFile;
    private static final String VIDEO_DIRECTORY_NAME = "AndroidWave";
    /**
     * An {@link AutoFitTextureView} for camera preview.
     */
    private AutoFitTextureView mTextureView;
    /**
     * A reference to the opened {@link CameraDevice}.
     */
    private CameraDevice mCameraDevice;
    /**
     * A reference to the current {@link CameraCaptureSession} for
     * preview.
     */
    private CameraCaptureSession mPreviewSession;
    /**
     * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
     * {@link TextureView}.
     */
    private TextureView.SurfaceTextureListener mSurfaceTextureListener
        = new TextureView.SurfaceTextureListener() {
      @Override
      public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
          int width, int height) {
        openCamera(width, height);
      }
      @Override
      public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
          int width, int height) {
        configureTransform(width, height);
      }
      @Override
      public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return true;
      }
      @Override
      public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
      }
    };
    /**
     * The {@link Size} of camera preview.
     */
    private Size mPreviewSize;
    /**
     * The {@link Size} of video recording.
     */
    private Size mVideoSize;
    /**
     * MediaRecorder
     */
    private MediaRecorder mMediaRecorder;
    /**
     * Whether the app is recording video now
     */
    public boolean mIsRecordingVideo;
    /**
     * An additional thread for running tasks that shouldn't block the UI.
     */
    private HandlerThread mBackgroundThread;
    /**
     * A {@link Handler} for running tasks in the background.
     */
    private Handler mBackgroundHandler;
    /**
     * A {@link Semaphore} to prevent the app from exiting before closing the camera.
     */
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    /**
     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status.
     */
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
      @Override
      public void onOpened(@NonNull CameraDevice cameraDevice) {
        mCameraDevice = cameraDevice;
        startPreview();
        mCameraOpenCloseLock.release();
        if (null != mTextureView) {
          configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
        }
      }
      @Override
      public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
      }
      @Override
      public void onError(@NonNull CameraDevice cameraDevice, int error) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
        Activity activity = getActivity();
        if (null != activity) {
          activity.finish();
        }
      }
    };
    private Integer mSensorOrientation;
    private CaptureRequest.Builder mPreviewBuilder;
    /**
     * In this sample, we choose a video size with 3x4 for  aspect ratio. for more perfectness 720
     * as well Also, we don't use sizes
     * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
     *
     * @param choices The list of available sizes
     * @return The video size 1080p,720px
     */
    private static Size chooseVideoSize(Size[] choices) {
      for (Size size : choices) {
        if (1920 == size.getWidth() && 1080 == size.getHeight()) {
          return size;
        }
      }
      for (Size size : choices) {
        if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
          return size;
        }
      }
      Log.e(TAG, "Couldn't find any suitable video size");
      return choices[choices.length - 1];
    }
    /**
     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
     * width and height are at least as large as the respective requested values, and whose aspect
     * ratio matches with the specified value.
     *
     * @param choices The list of sizes that the camera supports for the intended output class
     * @param width The minimum desired width
     * @param height The minimum desired height
     * @param aspectRatio The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */
    private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
      // Collect the supported resolutions that are at least as big as the preview Surface
      List<Size> bigEnough = new ArrayList<>();
      int w = aspectRatio.getWidth();
      int h = aspectRatio.getHeight();
      for (Size option : choices) {
        if (option.getHeight() == option.getWidth() * h / w &&
            option.getWidth() >= width && option.getHeight() >= height) {
          bigEnough.add(option);
        }
      }
      // Pick the smallest of those, assuming we found any
      if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
      } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
      }
    }
    public abstract int getTextureResource();
    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
      mTextureView = view.findViewById(getTextureResource());
    }
    @Override
    public void onResume() {
      super.onResume();
      startBackgroundThread();
      requestPermission();
    }
    @Override
    public void onPause() {
      closeCamera();
      stopBackgroundThread();
      super.onPause();
    }
    protected File getCurrentFile() {
      return mCurrentFile;
    }
    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
      mBackgroundThread = new HandlerThread("CameraBackground");
      mBackgroundThread.start();
      mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }
    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
      mBackgroundThread.quitSafely();
      try {
        mBackgroundThread.join();
        mBackgroundThread = null;
        mBackgroundHandler = null;
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    /**
     * Requesting permissions storage, audio and camera at once
     */
    public void requestPermission() {
      Dexter.withActivity(getActivity())
          .withPermissions(Manifest.permission.CAMERA,
              Manifest.permission.RECORD_AUDIO,
              Manifest.permission.READ_EXTERNAL_STORAGE,
              Manifest.permission.WRITE_EXTERNAL_STORAGE)
          .withListener(new MultiplePermissionsListener() {
            @Override
            public void onPermissionsChecked(MultiplePermissionsReport report) {
              // check if all permissions are granted or not
              if (report.areAllPermissionsGranted()) {
                if (mTextureView.isAvailable()) {
                  openCamera(mTextureView.getWidth(), mTextureView.getHeight());
                } else {
                  mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
                }
              }
              // check for permanent denial of any permission show alert dialog
              if (report.isAnyPermissionPermanentlyDenied()) {
                // open Settings activity
                showSettingsDialog();
              }
            }
            @Override
            public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions,
                PermissionToken token) {
              token.continuePermissionRequest();
            }
          })
          .withErrorListener(
              error -> Toast.makeText(getActivity().getApplicationContext(), "Error occurred! ",
                  Toast.LENGTH_SHORT).show())
          .onSameThread()
          .check();
    }
    /**
     * Showing Alert Dialog with Settings option in case of deny any permission
     */
    private void showSettingsDialog() {
      AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
      builder.setTitle(getString(R.string.message_need_permission));
      builder.setMessage(getString(R.string.message_permission));
      builder.setPositiveButton(getString(R.string.title_go_to_setting), (dialog, which) -> {
        dialog.cancel();
        openSettings();
      });
      builder.show();
    }
    // navigating settings app
    private void openSettings() {
      Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
      Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
      intent.setData(uri);
      startActivityForResult(intent, 101);
    }
    /**
     * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
     */
    private void openCamera(int width, int height) {
      final Activity activity = getActivity();
      if (null == activity || activity.isFinishing()) {
        return;
      }
      CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
      try {
        Log.d(TAG, "tryAcquire");
        if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
          throw new RuntimeException("Time out waiting to lock camera opening.");
        }
        /**
         * default front camera will activate
         */
        String cameraId = manager.getCameraIdList()[0];
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics
            .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        if (map == null) {
          throw new RuntimeException("Cannot get available preview/video sizes");
        }
        mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
            width, height, mVideoSize);
        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
          mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        } else {
          mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        }
        configureTransform(width, height);
        mMediaRecorder = new MediaRecorder();
        if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
          // TODO: Consider calling
          requestPermission();
          return;
        }
        manager.openCamera(cameraId, mStateCallback, null);
      } catch (CameraAccessException e) {
        Log.e(TAG, "openCamera: Cannot access the camera.");
      } catch (NullPointerException e) {
        Log.e(TAG, "Camera2API is not supported on the device.");
      } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera opening.");
      }
    }
    /**
     * Create directory and return file
     * returning video file
     */
    private File getOutputMediaFile() {
      // External sdcard file location
      File mediaStorageDir = new File(Environment.getExternalStorageDirectory(),
          VIDEO_DIRECTORY_NAME);
      // Create storage directory if it does not exist
      if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
          Log.d(TAG, "Oops! Failed create "
              + VIDEO_DIRECTORY_NAME + " directory");
          return null;
        }
      }
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
          Locale.getDefault()).format(new Date());
      File mediaFile;
      mediaFile = new File(mediaStorageDir.getPath() + File.separator
          + "VID_" + timeStamp + ".mp4");
      return mediaFile;
    }
    /**
     * close camera and release object
     */
    private void closeCamera() {
      try {
        mCameraOpenCloseLock.acquire();
        closePreviewSession();
        if (null != mCameraDevice) {
          mCameraDevice.close();
          mCameraDevice = null;
        }
        if (null != mMediaRecorder) {
          mMediaRecorder.release();
          mMediaRecorder = null;
        }
      } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera closing.");
      } finally {
        mCameraOpenCloseLock.release();
      }
    }
    /**
     * Start the camera preview.
     */
    private void startPreview() {
      if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
        return;
      }
      try {
        closePreviewSession();
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        Surface previewSurface = new Surface(texture);
        mPreviewBuilder.addTarget(previewSurface);
        mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
            new CameraCaptureSession.StateCallback() {
              @Override
              public void onConfigured(@NonNull CameraCaptureSession session) {
                mPreviewSession = session;
                updatePreview();
              }
              @Override
              public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                Log.e(TAG, "onConfigureFailed: Failed ");
              }
            }, mBackgroundHandler);
      } catch (CameraAccessException e) {
        e.printStackTrace();
      }
    }
    /**
     * Update the camera preview. {@link #startPreview()} needs to be called in advance.
     */
    private void updatePreview() {
      if (null == mCameraDevice) {
        return;
      }
      try {
        setUpCaptureRequestBuilder(mPreviewBuilder);
        HandlerThread thread = new HandlerThread("CameraPreview");
        thread.start();
        mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
      } catch (CameraAccessException e) {
        e.printStackTrace();
      }
    }
    private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
      builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    }
    /**
     * Configures the necessary {@link Matrix} transformation to `mTextureView`.
     * This method should not to be called until the camera preview size is determined in
     * openCamera, or until the size of `mTextureView` is fixed.
     *
     * @param viewWidth The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private void configureTransform(int viewWidth, int viewHeight) {
      Activity activity = getActivity();
      if (null == mTextureView || null == mPreviewSize || null == activity) {
        return;
      }
      int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
      Matrix matrix = new Matrix();
      RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
      RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
      float centerX = viewRect.centerX();
      float centerY = viewRect.centerY();
      if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max(
            (float) viewHeight / mPreviewSize.getHeight(),
            (float) viewWidth / mPreviewSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
      }
      mTextureView.setTransform(matrix);
    }
    private void setUpMediaRecorder() throws IOException {
      final Activity activity = getActivity();
      if (null == activity) {
        return;
      }
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      /**
       * create video output file
       */
      mCurrentFile = getOutputMediaFile();
      /**
       * set output file in media recorder
       */
      mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath());
      CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
      mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
      mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
      mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
      mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
      mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
      mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
      int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
      switch (mSensorOrientation) {
        case SENSOR_ORIENTATION_DEFAULT_DEGREES:
          mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
          break;
        case SENSOR_ORIENTATION_INVERSE_DEGREES:
          mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
          break;
      }
      mMediaRecorder.prepare();
    }
    public void startRecordingVideo() {
      if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
        return;
      }
      try {
        closePreviewSession();
        setUpMediaRecorder();
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        List<Surface> surfaces = new ArrayList<>();
        /**
         * Surface for the camera preview set up
         */
        Surface previewSurface = new Surface(texture);
        surfaces.add(previewSurface);
        mPreviewBuilder.addTarget(previewSurface);
        //MediaRecorder setup for surface
        Surface recorderSurface = mMediaRecorder.getSurface();
        surfaces.add(recorderSurface);
        mPreviewBuilder.addTarget(recorderSurface);
        // Start a capture session
        mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback()
        @Override
        public void onConfigured (@NonNull CameraCaptureSession cameraCaptureSession){
          mPreviewSession = cameraCaptureSession;
          updatePreview();
          getActivity().runOnUiThread(() -> {
            mIsRecordingVideo = true;
            // Start recording
            mMediaRecorder.start();
          });
        }
        @Override
        public void onConfigureFailed (@NonNull CameraCaptureSession cameraCaptureSession){
          Log.e(TAG, "onConfigureFailed: Failed");
        }
      },mBackgroundHandler);
    } catch(CameraAccessException |
    IOException e)
    {
      e.printStackTrace();
    }
  }
  private void closePreviewSession() {
    if (mPreviewSession != null) {
      mPreviewSession.close();
      mPreviewSession = null;
    }
  }
  public void stopRecordingVideo() throws Exception {
    // UI
    mIsRecordingVideo = false;
    try {
      mPreviewSession.stopRepeating();
      mPreviewSession.abortCaptures();
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
    // Stop recording
    mMediaRecorder.stop();
    mMediaRecorder.reset();
  }
  /**
   * Compares two {@code Size}s based on their areas.
   */
  static class CompareSizesByArea implements Comparator<Size> {
    @Override
    public int compare(Size lhs, Size rhs) {
      // We cast here to ensure the multiplications won't overflow
      return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
          (long) rhs.getWidth() * rhs.getHeight());
    }
  }
}

10. Now all utilities are done. Now comes on a real implementation.

10.1 Create a Fragment with the name of CameraFragment. Which extends CameraVideoFragment utility class
10.2 Create fragment_camera.xml. add below components
<?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=".CameraFragment"
    >
  <com.androidwave.camera2video.camera.AutoFitTextureView
      android:id="@+id/mTextureView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <ImageView
      android:id="@+id/mRecordVideo"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="24dp"
      android:contentDescription="@string/play_stop"
      android:src="@drawable/ic_record"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />
  <VideoView
      android:id="@+id/mVideoView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:visibility="gone"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <ImageView
      android:id="@+id/mPlayVideo"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:src="@drawable/ic_play_button"
      android:visibility="gone"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
</android.support.constraint.ConstraintLayout>
10.3. SetTextureResource by implement getTextureResource() methods of parent class
package com.androidwave.camera2video;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.VideoView;
import com.androidwave.camera2video.camera.AutoFitTextureView;
import com.androidwave.camera2video.camera.CameraVideoFragment;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
/**
 * A simple {@link Fragment} subclass.
 * Use the {@link CameraFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class CameraFragment extends CameraVideoFragment {
    @BindView(R.id.mTextureView)
    AutoFitTextureView mTextureView;
    @BindView(R.id.mRecordVideo)
    ImageView mRecordVideo;
    @BindView(R.id.mVideoView)
    VideoView mVideoView;
    @BindView(R.id.mPlayVideo)
    ImageView mPlayVideo;
    Unbinder unbinder;
    private String mOutputFilePath;
    public CameraFragment() {
        // Required empty public constructor
    }
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     */
    public static CameraFragment newInstance() {
        CameraFragment fragment = new CameraFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_camera, container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }
    @Override
    public int getTextureResource() {
        return R.id.mTextureView;
    }
    @Override
    protected void setUp(View view) {
    }
    @OnClick({R.id.mRecordVideo, R.id.mPlayVideo})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.mRecordVideo:
                /**
                 * If media is not recoding then start recording else stop recording
                 */
                if (mIsRecordingVideo) {
                    try {
                        stopRecordingVideo();
                        prepareViews();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    startRecordingVideo();
                    mRecordVideo.setImageResource(R.drawable.ic_stop);
                    //Receive out put file here
                    mOutputFilePath = getCurrentFile().getAbsolutePath();
                }
                break;
            case R.id.mPlayVideo:
                mVideoView.start();
                mPlayVideo.setVisibility(View.GONE);
                break;
        }
    }
    private void prepareViews() {
        if (mVideoView.getVisibility() == View.GONE) {
            mVideoView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.VISIBLE);
            mTextureView.setVisibility(View.GONE);
            setMediaForRecordVideo();
        }
    }
    private void setMediaForRecordVideo() {
        // Set media controller
        mVideoView.setMediaController(new MediaController(getActivity()));
        mVideoView.requestFocus();
        mVideoView.setVideoPath(mOutputFilePath);
        mVideoView.seekTo(100);
        mVideoView.setOnCompletionListener(mp -> {
            // Reset player
            mVideoView.setVisibility(View.GONE);
            mTextureView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.GONE);
            mRecordVideo.setImageResource(R.drawable.ic_record);
        });
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }
}

Step-11. As a result Fragment in with Activity seems below.

package com.androidwave.camera2video;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().hide();
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        if (null == savedInstanceState) {
            getFragmentManager().beginTransaction()
                    .replace(R.id.container, CameraFragment.newInstance())
                    .commit();
        }
    }
}

In this video recording android example, we have learned video recording using Camera2 API. Happy Coding 😁

Download Sample Project- Video Recording with Camera2 API in Android