介绍
Interactive Live流媒体已获得极大的知名度,为内容创建者,企业和开发人员提供了独特的机会,可以实时与听众建立联系。
在本教程中,您将学习如何使用Video SDK和Java在Android应用中集成Interactive Live流。
视频SDK是一种强大的工具,可让您将实时交互式流功能合并到应用程序中。借助Interactive实时流媒体,您可以让用户参与动态和沉浸式的体验,例如现场活动,游戏广播,虚拟教室等。
在本教程结束时,您将获得了将Interactive Live流入到您的Android应用程序中的宝贵技能,使您有能力为用户创造引人入胜的沉浸式体验。因此,让我们踏上这一旅程,并通过视频SDK解锁互动实时流的潜力!
先决条件
首先,您的开发环境应满足以下要求:
- Java Development Kit.
- Android Studio 3.0或更高版本。
- Android SDK API级别21或更高。
- 运行Android 5.0或更高版本的移动设备。
信息
应该有一个视频SDK帐户来生成令牌。访问视频SDK dashboard生成令牌
开始使用代码!
按照以下步骤创建必要的环境,以在应用程序中添加Interactive实时流。另外,您可以找到QuickStart here的代码样本。
创建新的Android项目
对于Android Studio中的新项目,创建一个具有空活动的电话和平板电脑项目。
视频SDK Android快速启动新项目
小心
创建项目后,Android Studio自动启动Gradle Sync。确保同步在继续之前成功。
集成视频SDK
Maven Central
settings.gradle
dependencyResolutionManagement {
repositories {
// ...
google()
mavenCentral()
maven { url "https://maven.aliyun.com/repository/jcenter" }
}
}
注意
您可以在RTC Android-SDK版本0.1.12之后使用Maven Central使用导入。无论是在Maven还是Jitpack上,相同的版本号始终参考相同的SDK。
在您的应用程序的app/build.gradle
中添加以下依赖性。
app/build.gradle
dependencies {implementation 'live.videosdk:rtc-android-sdk:0.1.17'
// library to perform Network call to generate a meeting idimplementation 'com.amitshekhar.android:android-networking:1.0.2'
// other app dependencies}
信息
Android SDK与armeabi-v7a
,arm64-v8a
,x86_64
体系结构兼容。如果要在模拟器中运行该应用程序,请在创建设备时选择ABIx86_64
。
将权限添加到您的项目中
在/app/Manifests/AndroidManifest.xml
中,添加</application>
之后的以下权限。
AndroidManifest.xml
<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
,则将android.enableJetifier = true
设置在gradle.properties
文件中以将您的项目迁移到Androidx并避免重复的类别冲突。
项目结构
您的项目结构应该看起来像这样:
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
注意
您必须将JoinActivity
设置为启动器活动。
使用视频SDK构建Android Interactive Live流应用程序的4个步骤
步骤1:创建加入屏幕
创建一个名为joinActivity的新活动。
创建UI
加入屏幕包括:
- 创建按钮:创建新的会议。
- textfield的会议ID:包含您要加入的会议ID。
- 加入作为主机按钮:加入会议作为主持人提供提供的会议。
- 加入查看器按钮:与提供的会议ID一起以观众的身份加入会议。
在/app/res/layout/activity_join.xml
文件中,用以下内容替换内容:
<?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>
集成创建会议API
- 在
JoinActivity
中创建FieldsampleToken
,该Field12将在Video SDK仪表板中保存生成的令牌。该令牌将用于VideosDK配置以及生成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) {
//...
}
}
-
在JOIN按钮作为主机
onClick
事件上,我们将使用令牌,会议和模式作为会议导航到MeetingActivity
。 -
在JOIN按钮上作为查看器
onClick
事件,我们将使用令牌,会议和模式作为查看器导航到MeetingActivity
。
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
}
}
- 对于
createMeeting
方法下的创建按钮,我们将通过拨打API并通过令牌,生成的会议ID和模式为CONFERENCE
来生成METICID。
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();
}
});
}
}
注意
不要在房间和会议关键字之间感到困惑,这两个都是同一件事。
- 我们的应用程序完全基于音频和视频通信,这就是为什么我们需要要求运行时权限
RECORD_AUDIO
和CAMERA
。因此,我们将在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);
}
}
步骤2:创建会议屏幕
创建一个名为MeetingActivity
的新活动。
为会议屏幕创建UI
在/app/res/layout/activity_meeting.xml
文件中,用以下内容替换内容。
<?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>
初始化会议
从JoinActivity
获得令牌,满足和模式后,
- 初始化视频SDK 。
- 配置**视频SDK **带有令牌。
- 用所需的参数(例如
meetingId
,participantName
,micEnabled
,webcamEnabled
,webcamEnabled
,mode
等)初始化会议。 - 使用
meeting.join()
方法加入房间。 - 添加
MeetingEventListener
以聆听会议加入活动。 - 检查
localParticipant
的模式,如果该模式为会议,我们将用SpeakerFragment
替换MainLayout,否则将用ViewerFragment
替换。
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, localParticipantName,
streamEnable, streamEnable, null, mode, false, 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;
}
}
步骤3:实施speakerviewâ
成功参加会议后,是时候渲染说话者的视图并管理控制型网络摄像头/麦克风,开始/停止HLS并离开会议了。
- 创建一个名为
SpeakerFragment
的新片段。 - 在
/app/res/layout/fragment_speaker.xml
文件中,用以下内容替换内容。
<?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>
- 现在,让我们设置侦听器的按钮,该按钮将允许参与者切换媒体。
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();
}
});
}
}
- 在添加按钮的侦听器后,让我们将
MeetingEventListener
添加到会议中,然后在onDestroy()
方法中删除所有听众。
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();
}
}
- 下一步是呈现说话者的观点。使用RecyClerview,我们将展示加入会议的主持人的参与者列表。
信息
- 在这里,参与者的视频是使用视频视频显示的,但是您也可以使用SurfaceViewRender进行。
- 对于视频,SDK版本应为0.1.13或更高。
- 要了解有关视频的更多信息,请访问here
- 为参与者视图创建一个新的布局,名为Item_remote_peer.xml在Res/Layout文件夹中。
<?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>
- 创建一个名为SpeakerAdapter的回收器视图适配器,该适配器将显示参与者列表。在适配器中创建Peerveiewholder,该适配器将扩展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);
}
}
}
- 将此适配器添加到
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));
}
}
步骤4:实现Viewerview
当主机开始实时流媒体时,观看者将能够看到实时流。
要实现播放器视图,我们将使用ExoPlayer
。玩HLS流将很有帮助。
,但首先,让我们在项目中添加依赖。
dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
// other app dependencies
}
创建一个名为ViewerFragment
的新片段
创建UIâ
观众片段将包括:
- 会议ID的文本视图 - 您加入的会议ID将显示在此文本视图中。
- 离开按钮 - 此按钮将离开会议。
- 等待layout - 这是当没有活动HLS时显示的TextView。
- stypledplayerview - 这是Mediaplayer,将显示直播。
在/app/res/layout/fragment_viewer.xml
文件中,用以下内容替换内容。
<?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>
初始化播放器并玩HLS流
初始化播放器并在HLS状态为HLS_PLAYABLE
时播放HLS,并在HLS状态为HLS_STOPPED
时将其发布。每当HLS状态更改会议时,将触发事件onHlsStateChanged
。
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();
}
}
最终输出
使用Video SDK和Java在Android中实现自定义的视频通话应用程序。要探索更多功能,请浏览基本和高级功能。
结论
恭喜!通过遵循本教程,您使用Video SDK成功地将Interactive Live流功能集成到了Android应用中。让我们回顾一下您已经完成的工作:
- 创建了一个用户友好的加入屏幕,该屏幕允许用户输入会议ID并选择加入作为主机或观看者。
- 实现了生成会议ID并处理不同角色(主机/查看器)的必要逻辑。
- 开发了一个会议屏幕,用户可以参加互动式实时流媒体会议,与流互动,与其他参与者聊天并进行实时讨论。
更多的Android资源
- Android Live Streaming App using Kotlin
- Build Android Video Calling App - docs
- Build Android Video Calling App using Android Studio and Video SDK
- 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