The HTTP Live Streaming (HLS) feature has gained huge popularity in recent times. It offers unique opportunities for content creators, businesses, and developers to connect with their audience in real-time.
The VideoSDK is a powerful tool that allows you to include real-time live streaming capabilities into your video call applications. With HTTP live streaming, you can engage your users in dynamic and immersive experiences, such as live events, gaming broadcasts, virtual classrooms, and more.
By the end of this tutorial, you will have acquired valuable skills in integrating live video streaming into your Android app, that creates engaging and immersive experiences for your users. So, let's embark on this journey and unlock the potential of live video streaming in Android with VideoSDK!
Integrating HLS with VideoSDK on Android
Follow the steps to create the necessary environment to add HTTP live streaming in your Android live app. You can find the code sample for Quickstart here.
Prerequisites
First of all, your development environment should meet the following requirements:
- Java Development Kit.
- Android Studio 3.0 or later.
- Android SDK API Level 21 or higher.
- A mobile device that runs Android 5.0 or later.
You should use a VideoSDK account to generate tokens. Visit VideoSDK dashboard to generate a token.
Create a new Android Project
For a new project in Android Studio, create a Phone and Tablet Android project with an Empty activity.
Video SDK Android Quick Start New Project
CAUTION: After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.
Add the repositories to the project's settings.gradle
file.
dependencyResolutionManagement{
repositories {
// ...
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://maven.aliyun.com/repository/jcenter" }
}
}
Add the following dependency to your app's app/build.gradle
.
dependencies {
implementation 'live.videosdk:rtc-android-sdk:0.1.26'
// library to perform Network call to generate a meeting id
implementation 'com.amitshekhar.android:android-networking:1.0.2'
// Other dependencies specific to your app
}
armeabi-v7a
, arm64-v8a
, x86_64
architectures. If you want to run the application in an emulator, choose ABI x86_64
when creating a device.Add permissions to your project
In /app/Manifests/AndroidManifest.xml
, add the following permissions after </application>
.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
android.useAndroidX = true
, then set android.enableJetifier = true
in the gradle.properties
file to migrate your project to AndroidX and avoid duplicate class conflicts.Structure of the project
Your project structure should look like this:
app
├── java
│ ├── packagename
│ ├── JoinActivity
│ ├── MeetingActivity
│ ├── SpeakerAdapter
│ ├── SpeakerFragment
| ├── ViewerFragment
├── res
│ ├── layout
│ │ ├── activity_join.xml
│ │ ├── activity_meeting.xml
| | ├── fragment_speaker.xml
| | ├── fragment_viewer.xml
│ │ ├── item_remote_peer.xml
NOTE: You have to set JoinActivity
as the Launcher activity.
App Architecture
Build an Android Live Video Streaming App
Step 1: Creating Joining Screen
Create a new Activity named JoinActivity
.
(a) Creating UI
The joining screen includes:
- Create Button: Creates a new meeting.
- TextField for Meeting ID: Contains the meeting ID you want to join.
- Join as Host Button: Joins the meeting as the host with the provided
meetingId
. - Join as Viewer Button: Joins the meeting as a viewer with the provided
meetingId
.
In the /app/res/layout/activity_join.xml
file, replace the content with the following:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/createorjoinlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btnCreateMeeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create Meeting"
android:textAllCaps="false" />
<TextView
android:id="@+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingVertical="5sp"
android:text="OR"
android:textColor="@color/white"
android:textSize="20sp" />
<EditText
android:id="@+id/etMeetingId"
android:theme="@android:style/Theme.Holo"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:hint="Enter Meeting Id"
android:textColor="@color/white"
android:textColorHint="@color/white" />
<Button
android:id="@+id/btnJoinHostMeeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8sp"
android:text="Join as Host"
android:textAllCaps="false" />
<Button
android:id="@+id/btnJoinViewerMeeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Join as Viewer"
android:textAllCaps="false" />
</LinearLayout>
(b) Integration of Create Meeting API
Create field sampleToken
in JoinActivity
that will hold the generated token from the VideoSDK dashboard. This token will be used in the VideoSDK config as well as in generating meetingId
.
public class JoinActivity extends AppCompatActivity {
//Replace with the token you generated from the VideoSDK Dashboard
private String sampleToken ="";
@Override
protected void onCreate(Bundle savedInstanceState) {
//...
}
}
On the Join Button as Host onClick
events, we will navigate to MeetingActivity
with token, meetingId
and mode as CONFERENCE
.
On the Join Button as Viewer onClick
events, we will navigate to MeetingActivity
with token, meetingId
and mode as Viewer
.
public class JoinActivity extends AppCompatActivity {
//Replace with the token you generated from the VideoSDK Dashboard
private String sampleToken ="";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_join);
final Button btnCreate = findViewById(R.id.btnCreateMeeting);
final Button btnJoinHost = findViewById(R.id.btnJoinHostMeeting);
final Button btnJoinViewer = findViewById(R.id.btnJoinViewerMeeting);
final EditText etMeetingId = findViewById(R.id.etMeetingId);
// create meeting and join as Host
btnCreate.setOnClickListener(v -> createMeeting(sampleToken));
// Join as Host
btnJoinHost.setOnClickListener(v -> {
Intent intent = new Intent(JoinActivity.this, MeetingActivity.class);
intent.putExtra("token", sampleToken);
intent.putExtra("meetingId", etMeetingId.getText().toString().trim());
intent.putExtra("mode", "CONFERENCE");
startActivity(intent);
});
// Join as Viewer
btnJoinViewer.setOnClickListener(v -> {
Intent intent = new Intent(JoinActivity.this, MeetingActivity.class);
intent.putExtra("token", sampleToken);
intent.putExtra("meetingId", etMeetingId.getText().toString().trim());
intent.putExtra("mode", "VIEWER");
startActivity(intent);
});
}
private void createMeeting(String token) {
// we will explore this method in the next step
}
}
For Create Button under createMeeting
method, we will generate meetingId
by calling API and navigating to MeetingActivity
with the token, generated meetingId
and mode as CONFERENCE
.
public class JoinActivity extends AppCompatActivity {
//...onCreate
private void createMeeting(String token) {
// we will make an API call to VideoSDK Server to get a roomId
AndroidNetworking.post("https://api.videosdk.live/v2/rooms")
.addHeaders("Authorization", token) //we will pass the token in the Headers
.build()
.getAsJSONObject(new JSONObjectRequestListener() {
@Override
public void onResponse(JSONObject response) {
try {
// response will contain `roomId`
final String meetingId = response.getString("roomId");
// starting the MeetingActivity with received roomId and our sampleToken
Intent intent = new Intent(JoinActivity.this, MeetingActivity.class);
intent.putExtra("token", sampleToken);
intent.putExtra("meetingId", meetingId);
intent.putExtra("mode", "CONFERENCE");
startActivity(intent);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onError(ANError anError) {
anError.printStackTrace();
Toast.makeText(JoinActivity.this, anError.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
Our Android live app is completely based on audio and video communication, that's why we need to ask for runtime permissions RECORD_AUDIO
and CAMERA
. So, we will implement permission logic on JoinActivity
.
public class JoinActivity extends AppCompatActivity {
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
private void checkSelfPermission(String permission, int requestCode) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//... button listeneres
checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID);
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID);
}
}
Step 2: Creating Meeting Screen
Create a new Activity named MeetingActivity
. In /app/res/layout/activity_meeting.xml
file, replace the content with the following.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".MeetingActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Creating a meeting for you"
android:textColor="@color/white"
android:textFontWeight="700"
android:textSize="20sp" />
</RelativeLayout>
(b) Initializing the Meeting
After getting the token, meetingId
and mode from JoinActivity
,
- Initialize VideoSDK.
- Configure VideoSDK with the token.
- Initialize the meeting with required params such as
meetingId
,participantName
,micEnabled
,webcamEnabled
,mode
and more. - Join the room with
meeting.join()
method. - Add
MeetingEventListener
for listening Meeting Join event. - Check mode of
localParticipant
, If the mode is CONFERENCE, We will replacemainLayout
withSpeakerFragment
otherwise, replace it withViewerFragment
.
public class MeetingActivity extends AppCompatActivity {
private Meeting meeting;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meeting);
final String meetingId = getIntent().getStringExtra("meetingId");
String token = getIntent().getStringExtra("token");
String mode = getIntent().getStringExtra("mode");
String localParticipantName = "John Doe";
boolean streamEnable = mode.equals("CONFERENCE");
// initialize VideoSDK
VideoSDK.initialize(getApplicationContext());
// Configuration VideoSDK with Token
VideoSDK.config(token);
// Initialize VideoSDK Meeting
meeting = VideoSDK.initMeeting(
MeetingActivity.this, meetingId, participantName,
micEnabled, webcamEnabled,null, null, false, null, null);
// join Meeting
meeting.join();
// if mode is CONFERENCE than replace mainLayout with SpeakerFragment otherwise with ViewerFragment
meeting.addEventListener(new MeetingEventListener() {
@Override
public void onMeetingJoined() {
if (meeting != null) {
if (mode.equals("CONFERENCE")) {
//pin the local partcipant
meeting.getLocalParticipant().pin("SHARE_AND_CAM");
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.mainLayout, new SpeakerFragment(), "MainFragment")
.commit();
} else if (mode.equals("VIEWER")) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.mainLayout, new ViewerFragment(), "viewerFragment")
.commit();
}
}
}
});
}
public Meeting getMeeting() {
return meeting;
}
}
Step 3: Implement SpeakerView
SpeakerView
After successfully entering the meeting, render the speaker's view and manage controls such as toggling the webcam/mic, start/stop HLS, and leaving the meeting.
Create a new fragment named SpeakerFragment
. In /app/res/layout/fragment_speaker.xml
file, replace the content with the following.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:gravity="center"
android:orientation="vertical"
tools:context=".SpeakerFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8sp"
android:paddingHorizontal="10sp">
<TextView
android:id="@+id/tvMeetingId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Meeting Id : "
android:textColor="@color/white"
android:textSize="18sp"
android:layout_weight="3"/>
<Button
android:id="@+id/btnLeave"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Leave"
android:textAllCaps="false"
android:layout_weight="1"/>
</LinearLayout>
<TextView
android:id="@+id/tvHlsState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Current HLS State : NOT_STARTED"
android:textColor="@color/white"
android:textSize="18sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvParticipants"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginVertical="10sp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:id="@+id/btnHLS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start HLS"
android:textAllCaps="false" />
<Button
android:id="@+id/btnWebcam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5sp"
android:text="Toggle Webcam"
android:textAllCaps="false" />
<Button
android:id="@+id/btnMic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Mic"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
Now, let's set the listener for buttons allowing the participant to toggle media.
public class SpeakerFragment extends Fragment {
private static Activity mActivity;
private static Context mContext;
private static Meeting meeting;
private boolean micEnabled = true;
private boolean webcamEnabled = true;
private boolean hlsEnabled = false;
private Button btnMic, btnWebcam, btnHls, btnLeave;
private TextView tvMeetingId, tvHlsState;
public SpeakerFragment() {
// Required empty public constructor
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context;
if (context instanceof Activity) {
mActivity = (Activity) context;
// getting meeting object from Meeting Activity
meeting = ((MeetingActivity) mActivity).getMeeting();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_speaker, container, false);
btnMic = view.findViewById(R.id.btnMic);
btnWebcam = view.findViewById(R.id.btnWebcam);
btnHls = view.findViewById(R.id.btnHLS);
btnLeave = view.findViewById(R.id.btnLeave);
tvMeetingId = view.findViewById(R.id.tvMeetingId);
tvHlsState = view.findViewById(R.id.tvHlsState);
if (meeting != null) {
tvMeetingId.setText("Meeting Id : " + meeting.getMeetingId());
setActionListeners();
}
return view;
}
private void setActionListeners() {
btnMic.setOnClickListener(v -> {
if (micEnabled) {
meeting.muteMic();
Toast.makeText(mContext,"Mic Muted",Toast.LENGTH_SHORT).show();
} else {
meeting.unmuteMic();
Toast.makeText(mContext,"Mic Enabled",Toast.LENGTH_SHORT).show();
}
micEnabled=!micEnabled;
});
btnWebcam.setOnClickListener(v -> {
if (webcamEnabled) {
meeting.disableWebcam();
Toast.makeText(mContext,"Webcam Disabled",Toast.LENGTH_SHORT).show();
} else {
meeting.enableWebcam();
Toast.makeText(mContext,"Webcam Enabled",Toast.LENGTH_SHORT).show();
}
webcamEnabled=!webcamEnabled;
});
btnLeave.setOnClickListener(v -> meeting.leave());
btnHls.setOnClickListener(v -> {
if (!hlsEnabled) {
JSONObject config = new JSONObject();
JSONObject layout = new JSONObject();
JsonUtils.jsonPut(layout, "type", "SPOTLIGHT");
JsonUtils.jsonPut(layout, "priority", "PIN");
JsonUtils.jsonPut(layout, "gridSize", 4);
JsonUtils.jsonPut(config, "layout", layout);
JsonUtils.jsonPut(config, "orientation", "portrait");
JsonUtils.jsonPut(config, "theme", "DARK");
JsonUtils.jsonPut(config, "quality", "high");
meeting.startHls(config);
} else {
meeting.stopHls();
}
});
}
}
After adding listeners for buttons, let's add MeetingEventListener
to the meeting and remove all the listeners in onDestroy()
method.
public class SpeakerFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//...
if (meeting != null) {
//...
// add Listener to the meeting
meeting.addEventListener(meetingEventListener);
}
return view;
}
private final MeetingEventListener meetingEventListener = new MeetingEventListener() {
@Override
public void onMeetingLeft() {
//unpin local participant
meeting.getLocalParticipant().unpin("SHARE_AND_CAM");
if (isAdded()) {
Intent intents = new Intent(mContext, JoinActivity.class);
intents.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intents);
mActivity.finish();
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
@Override
public void onHlsStateChanged(JSONObject HlsState) {
if (HlsState.has("status")) {
try {
tvHlsState.setText("Current HLS State : " + HlsState.getString("status"));
if (HlsState.getString("status").equals("HLS_STARTED")) {
hlsEnabled=true;
btnHls.setText("Stop HLS");
}
if (HlsState.getString("status").equals("HLS_STOPPED")) {
hlsEnabled = false;
btnHls.setText("Start HLS");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
};
@Override
public void onDestroy() {
mContext = null;
mActivity = null;
if (meeting != null) {
meeting.removeAllListeners();
meeting = null;
}
super.onDestroy();
}
}
The next step is to render the speaker's view. With RecyclerView
, we will display the list of participants who have joined the meeting as host
.
VideoView
, but you may also use SurfaceViewRender
for the same.- For
VideoView
, SDK version should be 0.1.13
or higher.- To know more about
VideoView
, please visit here.Create a new layout for the participant view named item_remote_peer.xml
in the res/layout
folder.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="200dp"
android:background="@color/cardview_dark_background"
tools:layout_height="200dp">
<live.videosdk.rtc.android.VideoView
android:id="@+id/participantView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#99000000"
android:orientation="horizontal">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="4dp"
android:textColor="@color/white" />
</LinearLayout>
</FrameLayout>
Create a recycler view adapter SpeakerAdapter
that will show the participant list. Create PeerViewHolder
in an adapter that will extend RecyclerView.ViewHolder
.
public class SpeakerAdapter extends RecyclerView.Adapter<SpeakerAdapter.PeerViewHolder> {
private List<Participant> participantList = new ArrayList<>();
private final Meeting meeting;
public SpeakerAdapter(Meeting meeting) {
this.meeting = meeting;
updateParticipantList();
// adding Meeting Event listener to get the participant join/leave event in the meeting.
meeting.addEventListener(new MeetingEventListener() {
@Override
public void onParticipantJoined(Participant participant) {
// check participant join as Host/Speaker or not
if (participant.getMode().equals("CONFERENCE")) {
// pin the participant
participant.pin("SHARE_AND_CAM");
// add participant in participantList
participantList.add(participant);
}
notifyDataSetChanged();
}
@Override
public void onParticipantLeft(Participant participant) {
int pos = -1;
for (int i = 0; i < participantList.size(); i++) {
if (participantList.get(i).getId().equals(participant.getId())) {
pos = i;
break;
}
}
if(participantList.contains(participant)) {
// unpin participant who left the meeting
participant.unpin("SHARE_AND_CAM");
// remove participant from the list
participantList.remove(participant);
}
if (pos >= 0) {
notifyItemRemoved(pos);
}
}
});
}
private void updateParticipantList() {
participantList = new ArrayList<>();
// adding the local participant(You) to the list
participantList.add(meeting.getLocalParticipant());
// adding participants who join as Host/Speaker
Iterator<Participant> participants = meeting.getParticipants().values().iterator();
for (int i = 0; i < meeting.getParticipants().size(); i++) {
final Participant participant = participants.next();
if (participant.getMode().equals("CONFERENCE")) {
// pin the participant
participant.pin("SHARE_AND_CAM");
// add participant in participantList
participantList.add(participant);
}
}
}
@NonNull
@Override
public PeerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new PeerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_remote_peer, parent, false));
}
@Override
public void onBindViewHolder(@NonNull PeerViewHolder holder, int position) {
Participant participant = participantList.get(position);
holder.tvName.setText(participant.getDisplayName());
// adding the initial video stream for the participant into the 'VideoView'
for (Map.Entry<String, Stream> entry : participant.getStreams().entrySet()) {
Stream stream = entry.getValue();
if (stream.getKind().equalsIgnoreCase("video")) {
holder.participantView.setVisibility(View.VISIBLE);
VideoTrack videoTrack = (VideoTrack) stream.getTrack();
holder.participantView.addTrack(videoTrack);
break;
}
}
// add Listener to the participant which will update start or stop the video stream of that participant
participant.addEventListener(new ParticipantEventListener() {
@Override
public void onStreamEnabled(Stream stream) {
if (stream.getKind().equalsIgnoreCase("video")) {
holder.participantView.setVisibility(View.VISIBLE);
VideoTrack videoTrack = (VideoTrack) stream.getTrack();
holder.participantView.addTrack(videoTrack);
}
}
@Override
public void onStreamDisabled(Stream stream) {
if (stream.getKind().equalsIgnoreCase("video")) {
holder.participantView.removeTrack();
holder.participantView.setVisibility(View.GONE);
}
}
});
}
@Override
public int getItemCount() {
return participantList.size();
}
static class PeerViewHolder extends RecyclerView.ViewHolder {
// 'VideoView' to show Video Stream
public VideoView participantView;
public TextView tvName;
public View itemView;
PeerViewHolder(@NonNull View view) {
super(view);
itemView = view;
tvName = view.findViewById(R.id.tvName);
participantView = view.findViewById(R.id.participantView);
}
}
}
Add this adapter to the SpeakerFragment
.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//...
if (meeting != null) {
//...
final RecyclerView rvParticipants = view.findViewById(R.id.rvParticipants);
rvParticipants.setLayoutManager(new GridLayoutManager(mContext, 2));
rvParticipants.setAdapter(new SpeakerAdapter(meeting));
}
}
Step 4: Implement ViewerView
When the host starts live streaming, The viewer can see the live streaming.
To implement the player view, We're going to use ExoPlayer
. It will be helpful to play the HLS stream.
But first, Let's add a dependency to the project.
dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
// other app dependencies
}
Create a new Fragment named ViewerFragment
(a) Creating UI
The Viewer Fragment will include :
- TextView for Meeting ID - The meeting ID that you joined will be displayed in this text view.
- Leave Button - This button will leave the meeting.
- waitingLayout - This textView will be shown when there is no active HLS.
- StyledPlayerView - This is a media player that will display live streaming.
In /app/res/layout/fragment_viewer.xml
file, replace the content with the following.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="@color/black"
tools:context=".ViewerFragment">
<LinearLayout
android:id="@+id/meetingLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="12sp"
android:paddingVertical="5sp">
<TextView
android:id="@+id/meetingId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="Meeting Id : "
android:textColor="@color/white"
android:textSize="20sp" />
<Button
android:id="@+id/btnLeave"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Leave" />
</LinearLayout>
<TextView
android:id="@+id/waitingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Waiting for host \n to start the live streaming"
android:textColor="@color/white"
android:textFontWeight="700"
android:textSize="20sp"
android:gravity="center"/>
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:resize_mode="fixed_width"
app:show_buffering="when_playing"
app:show_subtitle_button="false"
app:use_artwork="false"
app:show_next_button="false"
app:show_previous_button="false"
app:use_controller="true"
android:layout_below="@id/meetingLayout"/>
</RelativeLayout>
(b) Initialize player and Play HLS stream
Initialize the player and play the HLS when the HLS state is HLS_PLAYABLE
, and release it when the HLS state is HLS_STOPPED
. Whenever the meeting HLS state changes, the event onHlsStateChanged
will be triggered.
public class ViewerFragment extends Fragment {
private Meeting meeting;
protected StyledPlayerView playerView;
private TextView waitingLayout;
protected @Nullable
ExoPlayer player;
private DefaultHttpDataSource.Factory dataSourceFactory;
private boolean startAutoPlay=true;
private String downStreamUrl = "";
private static Activity mActivity;
private static Context mContext;
public ViewerFragment() {
// Required empty public constructor
}
@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_viewer, container, false);
playerView = view.findViewById(R.id.player_view);
waitingLayout = view.findViewById(R.id.waitingLayout);
if(meeting != null) {
// set MeetingId to TextView
((TextView) view.findViewById(R.id.meetingId)).setText("Meeting Id : " + meeting.getMeetingId());
// leave the meeting on btnLeave click
((Button) view.findViewById(R.id.btnLeave)).setOnClickListener(v -> meeting.leave());
// add listener to meeting
meeting.addEventListener(meetingEventListener);
}
return view;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context;
if (context instanceof Activity) {
mActivity = (Activity) context;
// get meeting object from MeetingActivity
meeting = ((MeetingActivity) mActivity).getMeeting();
}
}
private final MeetingEventListener meetingEventListener = new MeetingEventListener() {
@Override
public void onMeetingLeft() {
if (isAdded()) {
Intent intents = new Intent(mContext, JoinActivity.class);
intents.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intents);
mActivity.finish();
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
@Override
public void onHlsStateChanged(JSONObject HlsState) {
if (HlsState.has("status")) {
try {
if (HlsState.getString("status").equals("HLS_PLAYABLE") && HlsState.has("downstreamUrl")) {
downStreamUrl = HlsState.getString("downstreamUrl");
waitingLayout.setVisibility(View.GONE);
playerView.setVisibility(View.VISIBLE);
// initialize player
initializePlayer();
}
if (HlsState.getString("status").equals("HLS_STOPPED")) {
// release the player
releasePlayer();
downStreamUrl = null;
waitingLayout.setText("Host has stopped \n the live streaming");
waitingLayout.setVisibility(View.VISIBLE);
playerView.setVisibility(View.GONE);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
};
protected void initializePlayer() {
if (player == null) {
dataSourceFactory = new DefaultHttpDataSource.Factory();
HlsMediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
MediaItem.fromUri(Uri.parse(this.downStreamUrl)));
ExoPlayer.Builder playerBuilder =
new ExoPlayer.Builder(/* context= */ mContext);
player = playerBuilder.build();
// auto play when player is ready
player.setPlayWhenReady(startAutoPlay);
player.setMediaSource(mediaSource);
// if you want display setting for player then remove this line
playerView.findViewById(com.google.android.exoplayer2.ui.R.id.exo_settings).setVisibility(View.GONE);
playerView.setPlayer(player);
}
player.prepare();
}
protected void releasePlayer() {
if (player != null) {
player.release();
player = null;
dataSourceFactory = null;
playerView.setPlayer(/* player= */ null);
}
}
@Override
public void onDestroy() {
mContext = null;
mActivity = null;
downStreamUrl = null;
releasePlayer();
if (meeting != null) {
meeting.removeAllListeners();
meeting = null;
}
super.onDestroy();
}
}
Final Output
Congratulations! Following this tutorial, you have successfully integrated HTTP live streaming capabilities into your Android app using VideoSDK. Let's recap what you have accomplished:
- Created a user-friendly joining screen allowing users to enter a meeting ID and join as a host or viewer.
- Implemented the necessary logic to generate meeting IDs and handle different roles (host/viewer).
- Developed a meeting screen where users can participate in HTTP live-streaming sessions, engage with the stream, chat with other participants, and have real-time discussions.
To unlock the full potential of VideoSDK and create easy-to-use video experiences, developers are encouraged to sign up for VideoSDK and further explore its features.
Sign up with VideoSDK today and Get 10000 minutes free to take your Android video app to the next level!
More Android Resources
- Android One-To-One Video Calling App with Java
- Android Live Streaming App using Kotlin
- Build Android Video Calling App - docs
- Build Android Video Calling App using Android Studio and VideoSDK
- quickstart/android-rtc
- quickstart/android-hls
- videosdk-rtc-android-java-sdk-example
- videosdk-rtc-android-kotlin-sdk-example
- videosdk-hls-android-java-example
- videosdk-hls-android-kotlin-example