如果您是在线内容的狂热消费者,您通常会消耗大量的实时流。随着直播成为最喜欢的学习和娱乐来源,很难错过现场广播。无论是参加体育赛事,参加在线课程,观看健身课程还是与名人互动。
许多实时流媒体应用程序都依赖于HTTP实时流媒体(HLS - 一种广泛使用的协议)来向受众传递内容。实际上,如果您曾经观看过Instagram直播,那么您已经有了HLS的魔力。
如果您是一个希望在Android应用中构建一流的直播体验的开发人员,则本文适合您。
为什么选择视频SDK?
视频SDK是那些寻求实时流媒体平台的人的理想选择,该平台提供了创建高质量流的功能。该平台支持各种功能,例如屏幕共享,实时消息传递,允许广播公司邀请观众进入舞台并支持100名参与者,从而确保您的实时流互动和引人入胜。使用Video SDK,您还可以使用自己的自定义设计布局模板进行实时流媒体。
在集成方面,视频SDK非常简单且快速地集成,这使您可以无缝地集成在应用中的实时流。这样可以确保您可以在没有任何技术困难或冗长的实施过程的情况下享受现场流的好处。
此外,Video SDK对预算友好,使其成为各种规模的企业的负担得起的选择。您可以享受功能丰富的实时流媒体平台的好处,而不会破坏银行,这是初创企业和小型企业的理想选择。
构建现场流媒体应用程序
以下步骤将为您提供所有信息,以快速构建交互式实时流媒体应用程序。请仔细跟随。有麻烦吗?让我们立即在不和谐上知道,我们很乐意为您提供帮助。
先决条件
- Java开发套件
- Android Studio 3.0或更高版本。
- Android SDK API级别21或更高。
- 运行Android 5.0或更高版本的移动设备。
- 视频SDK仪表板的令牌
设置项目
创建新项目
集成视频SDK
- 将存储库添加到Project的设置.gradle文件中。
dependencyResolutionManagement{
repositories {
// ...
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://maven.aliyun.com/repository/jcenter" }
}
}
- 在您的应用程序build.gradle中添加以下依赖关系。
dependencies {
implementation 'live.videosdk:rtc-android-sdk:0.1.15'
// library to perform Network call to generate a meeting id
implementation 'com.amitshekhar.android:android-networking:1.0.2'
// other app dependencies
}
如果您的项目已设置android.useandroidx = true,则在gradle.properties文件中设置android.enableJetifier = true。将您的项目迁移到Androidx并避免重复的类别冲突。
。将权限添加到您的项目中
在/app/Manifests/AndroidManifest.xml
中,添加</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" />
项目的结构
我们将创建两个活动和两个片段。第一个活动是JoinActivity
â,它允许用户创建/加入会议,另一个是MeetingActivity
â,它将根据用户的选择初始化会议并用SpeakerFragment
或ViewerFragment
替换mainLayout
。
我们的项目结构看起来像这样。
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.xmlCopy
您必须将加入性设置为发射器活动。
应用结构
步骤1:创建加入屏幕
创建一个名为JoinActivity
创建UI以加入屏幕
连接屏幕将包括:
- 创建按钮 - 此按钮将为您创建新的会议。
- textfield进行会议IDâââââ€将包含您要加入的会议ID。
- 加入主机按钮 - 此按钮将与您提供的
meetingId
一起加入会议。 - 加入查看器按钮 - 此按钮将与您提供的
meetingId
一起参加会议。
在/app/res/layout/activity_join.xml
文件中,用以下内容替换内容。
<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
,该field11将在VideoSDK dashboard中保存生成的令牌。该令牌将用于VideoSDK配置以及生成METICID。
class JoinActivity : AppCompatActivity() {
//Replace with the token you generated from the VideoSDK Dashboard
private var sampleToken = ""
override fun onCreate(savedInstanceState: Bundle?) {
//...
}
}
-
在JOIN按钮作为主持人onclick事件上,我们将Naviagte与令牌,会议和模式作为会议。
-
在加入按钮上作为观看器onClick事件,我们将Naviagte与令牌,MeetingID和Mode作为查看器的会议。
class JoinActivity : AppCompatActivity() {
//Replace with the token you generated from the VideoSDK Dashboard
private var sampleToken = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_join)
val btnCreate = findViewById<Button>(R.id.btnCreateMeeting)
val btnJoinHost = findViewById<Button>(R.id.btnJoinHostMeeting)
val btnJoinViewer = findViewById<Button>(R.id.btnJoinViewerMeeting)
val etMeetingId = findViewById<EditText>(R.id.etMeetingId)
// create meeting and join as Host
btnCreate.setOnClickListener {
createMeeting(
sampleToken
)
}
// Join as Host
btnJoinHost.setOnClickListener {
val intent = Intent(this@JoinActivity, MeetingActivity::class.java)
intent.putExtra("token", sampleToken)
intent.putExtra("meetingId", etMeetingId.text.toString().trim { it <= ' ' })
intent.putExtra("mode", "CONFERENCE")
startActivity(intent)
}
// Join as Viewer
btnJoinViewer.setOnClickListener {
val intent = Intent(this@JoinActivity, MeetingActivity::class.java)
intent.putExtra("token", sampleToken)
intent.putExtra("meetingId", etMeetingId.text.toString().trim { it <= ' ' })
intent.putExtra("mode", "VIEWER")
startActivity(intent)
}
}
private fun createMeeting(token: String) {
// we will explore this method in the next step
}
- 对于创建按钮,在创建方法下,我们将通过调用API并导航以与令牌,生成的会议ID和模式作为会议来生成oferitid。
class JoinActivity : AppCompatActivity() {
//...onCreate
private fun createMeeting(token: String) {
// 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(object : JSONObjectRequestListener {
override fun onResponse(response: JSONObject) {
try {
// response will contain `roomId`
val meetingId = response.getString("roomId")
// starting the MeetingActivity with received roomId and our sampleToken
val intent = Intent(this@JoinActivity, MeetingActivity::class.java)
intent.putExtra("token", sampleToken)
intent.putExtra("meetingId", meetingId)
intent.putExtra("mode", "CONFERENCE")
startActivity(intent)
} catch (e: JSONException) {
e.printStackTrace()
}
}
override fun onError(anError: ANError) {
anError.printStackTrace()
Toast.makeText(this@JoinActivity, anError.message, Toast.LENGTH_SHORT)
.show()
}
})
}
}
- 我们的应用程序完全基于音频和视频通勤,这就是为什么我们需要要求运行时许可
RECORD_AUDIO
和CAMERA
。因此,我们将在JoinActivity
上实现权限逻辑。
class JoinActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQ_ID = 22
private val REQUESTED_PERMISSIONS = arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
)
}
private fun checkSelfPermission(permission: String, requestCode: Int) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
//... button listeneres
checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID)
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)
}
}
您将获得未解决的参考:会议活动错误,但请不要担心。创建会议之后,它将自动解决。
步骤2:创建会议屏幕
创建一个名为“会议”的新活动。
创建用于会议屏幕的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
获得令牌后,遇见和模式后,
- 初始化videosdk。
- 用令牌配置VideoSdk。
- 以所需的参数(例如
meetingId
,participantName
,micEnabled
,webcamEnabled
,mode
等)初始化会议。 - 使用
meeting.join()
方法加入房间。 - 添加
MeetingEventListener
进行收听会议加入活动。 - 检查
localParticipant
的模式,如果模式是会议,那么我们将用SpeakerFragment
替换mainlayout,否则用ViewerFragment
替换。
class MeetingActivity : AppCompatActivity() {
var meeting: Meeting? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meeting)
val meetingId = intent.getStringExtra("meetingId")
val token = intent.getStringExtra("token")
val mode = intent.getStringExtra("mode")
val localParticipantName = "John Doe"
val streamEnable = mode == "CONFERENCE"
// initialize VideoSDK
VideoSDK.initialize(applicationContext)
// Configuration VideoSDK with Token
VideoSDK.config(token)
// Initialize VideoSDK Meeting
meeting = VideoSDK.initMeeting(
this@MeetingActivity, meetingId, localParticipantName,
streamEnable, streamEnable, null, mode, null
)
// join Meeting
meeting!!.join()
// if mode is CONFERENCE than replace mainLayout with SpeakerFragment otherwise with ViewerFragment
meeting!!.addEventListener(object : MeetingEventListener() {
override fun onMeetingJoined() {
if (meeting != null) {
if (mode == "CONFERENCE") {
//pin the local partcipant
meeting!!.localParticipant.pin("SHARE_AND_CAM")
supportFragmentManager
.beginTransaction()
.replace(R.id.mainLayout, SpeakerFragment(), "MainFragment")
.commit()
} else if (mode == "VIEWER") {
supportFragmentManager
.beginTransaction()
.replace(R.id.mainLayout, ViewerFragment(), "viewerFragment")
.commit()
}
}
}
})
}
}
步骤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>
- 现在,让S设置按钮的侦听器,该按钮将允许参与者切换媒体。
class SpeakerFragment : Fragment() {
private var micEnabled = true
private var webcamEnabled = true
private var hlsEnabled = false
private var btnMic: Button? = null
private var btnWebcam: Button? = null
private var btnHls: Button? = null
private var btnLeave: Button? = null
private var tvMeetingId: TextView? = null
private var tvHlsState: TextView? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
if (context is Activity) {
mActivity = context
// getting meeting object from Meeting Activity
meeting = (mActivity as MeetingActivity?)!!.meeting
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val 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!!.text = "Meeting Id : " + meeting!!.meetingId
setActionListeners()
}
return view
}
private fun setActionListeners() {}
companion object {
private var mActivity: Activity? = null
private var mContext: Context? = null
private var meeting: Meeting? = null
}
}
private fun setActionListeners() {
btnMic!!.setOnClickListener {
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 {
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 { meeting!!.leave() }
btnHls!!.setOnClickListener {
if (!hlsEnabled) {
val config = JSONObject()
val layout = 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()
}
}
}
- 在添加按钮的列表之后,让我们在会议中添加ceetingEventListener,然后在ondestroy()方法中删除所有列表。
class SpeakerFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//...
if (meeting != null) {
//...
// add Listener to the meeting
meeting!!.addEventListener(meetingEventListener)
}
return view
}
private val meetingEventListener: MeetingEventListener = object : MeetingEventListener() {
override fun onMeetingLeft() {
// unpin the local participant
meeting!!.localParticipant.unpin("SHARE_AND_CAM")
if (isAdded) {
val intents = Intent(mContext, JoinActivity::class.java)
intents.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK
)
startActivity(intents)
mActivity!!.finish()
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
override fun onHlsStateChanged(HlsState: JSONObject) {
if (HlsState.has("status")) {
try {
tvHlsState!!.text = "Current HLS State : " + HlsState.getString("status")
if (HlsState.getString("status") == "HLS_STARTED") {
hlsEnabled = true
btnHls!!.text = "Stop HLS"
}
if (HlsState.getString("status") == "HLS_STOPPED") {
hlsEnabled = false
btnHls!!.text = "Start HLS"
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
}
override fun onDestroy() {
mContext = null
mActivity = null
if (meeting != null) {
meeting!!.removeAllListeners()
meeting = null
}
super.onDestroy()
}
}
- 下一步是呈现说话者的观点。借助Recyclerview,我们将展示一份参加会议的参与者列表。
- 为
res/layout folder
中的名为item_remote_peer.xml
的参与者视图创建一个新的布局。
<?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。
class SpeakerAdapter(private val meeting: Meeting) :
RecyclerView.Adapter<SpeakerAdapter.PeerViewHolder?>() {
private var participantList: MutableList<Participant> = ArrayList()
init {
updateParticipantList()
// adding Meeting Event listener to get the participant join/leave event in the meeting.
meeting.addEventListener(object : MeetingEventListener() {
override fun onParticipantJoined(participant: Participant) {
// check participant join as Host/Speaker or not
if (participant.mode == "CONFERENCE") {
// pin the participant
participant.pin("SHARE_AND_CAM")
// add participant in participantList
participantList.add(participant)
}
notifyDataSetChanged()
}
override fun onParticipantLeft(participant: Participant) {
var pos = -1
for (i in participantList.indices) {
if (participantList[i].id == participant.id) {
pos = i
break
}
}
if (participantList.contains(participant)) {
// unpin participant who left the meeting
participant.unpin("SHARE_AND_CAM")
// remove participant from participantList
participantList.remove(participant)
}
if (pos >= 0) {
notifyItemRemoved(pos)
}
}
})
}
private fun updateParticipantList() {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PeerViewHolder {
}
override fun onBindViewHolder(holder: PeerViewHolder, position: Int) {
}
override fun getItemCount(): Int {
}
class PeerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
}
private fun updateParticipantList() {
// adding the local participant(You) to the list
participantList.add(meeting.localParticipant)
// adding participants who join as Host/Speaker
val participants: Iterator<Participant> = meeting.participants.values.iterator()
for (i in 0 until meeting.participants.size) {
val participant = participants.next()
if (participant.mode == "CONFERENCE") {
// pin the participant
participant.pin("SHARE_AND_CAM")
// add participant in participantList
participantList.add(participant)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PeerViewHolder {
return PeerViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_remote_peer, parent, false)
)
}
override fun onBindViewHolder(holder: PeerViewHolder, position: Int) {
val participant = participantList[position]
holder.tvName.text = participant.displayName
// adding the initial video stream for the participant into the 'VideoView'
for ((_, stream) in participant.streams) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.visibility = View.VISIBLE
val videoTrack = stream.track as VideoTrack
holder.participantView.addTrack(videoTrack)
break
}
}
// add Listener to the participant which will update start or stop the video stream of that participant
participant.addEventListener(object : ParticipantEventListener() {
override fun onStreamEnabled(stream: Stream) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.visibility = View.VISIBLE
val videoTrack = stream.track as VideoTrack
holder.participantView.addTrack(videoTrack)
}
}
override fun onStreamDisabled(stream: Stream) {
if (stream.kind.equals("video", ignoreCase = true)) {
holder.participantView.removeTrack()
holder.participantView.visibility = View.GONE
}
}
})
}
override fun getItemCount(): Int {
return participantList.size
}
class PeerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
// 'VideoView' to show Video Stream
var participantView: VideoView
var tvName: TextView
init {
tvName = view.findViewById(R.id.tvName)
participantView = view.findViewById(R.id.participantView)
}
}
- 将此适配器添加到
SpeakerFragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//...
if (meeting != null) {
//...
val rvParticipants = view.findViewById<RecyclerView>(R.id.rvParticipants)
rvParticipants.layoutManager = GridLayoutManager(mContext, 2)
rvParticipants.adapter = SpeakerAdapter(meeting!!)
}
}
步骤4:实现Viewerview
当主机开始实时流媒体时,观看者将能够看到实时流。
要实现播放器视图,我们将使用epeplayer。玩HLS流将很有帮助。
让我们首先将依赖性添加到项目中。
dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
// other app dependencies
}
创建一个名为vieverfragment的新片段。
为观众片段创建UI
观众片段将包括:
- 您加入的会议ID会议ID的文本视图将在此文本视图中显示。
- 离开按钮 - 此按钮将离开会议。
- 等待LAEDLAYOUT - 这是当没有活动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_plays时播放HLS,并在HLS状态为HLS_STOP时将其发布。每当HLS状态发生变化时,将触发事件Onhlsstatatechang。
class ViewerFragment : Fragment() {
private var meeting: Meeting? = null
private var playerView: StyledPlayerView? = null
private var waitingLayout: TextView? = null
private var player: ExoPlayer? = null
private var dataSourceFactory: DefaultHttpDataSource.Factory? = null
private val startAutoPlay = true
private var downStreamUrl: String? = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val 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
(view.findViewById<View>(R.id.meetingId) as TextView).text =
"Meeting Id : " + meeting!!.meetingId
// leave the meeting on btnLeave click
(view.findViewById<View>(R.id.btnLeave) as Button).setOnClickListener { meeting!!.leave() }
// add listener to meeting
meeting!!.addEventListener(meetingEventListener)
}
return view
}
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
if (context is Activity) {
mActivity = context
// get meeting object from MeetingActivity
meeting = (mActivity as MeetingActivity?)!!.meeting
}
}
private val meetingEventListener: MeetingEventListener = object : MeetingEventListener() {}
override fun onDestroy() {
}
companion object {
private var mActivity: Activity? = null
private var mContext: Context? = null
}
}
private val meetingEventListener: MeetingEventListener = object : MeetingEventListener() {
override fun onMeetingLeft() {
if (isAdded) {
val intents = Intent(mContext, JoinActivity::class.java)
intents.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK
)
startActivity(intents)
mActivity!!.finish()
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
override fun onHlsStateChanged(HlsState: JSONObject) {
if (HlsState.has("status")) {
try {
if (HlsState.getString("status") == "HLS_PLAYABLE" && HlsState.has("downstreamUrl")) {
downStreamUrl = HlsState.getString("downstreamUrl")
waitingLayout!!.visibility = View.GONE
playerView!!.visibility = View.VISIBLE
// initialize player
initializePlayer()
}
if (HlsState.getString("status") == "HLS_STOPPED") {
// release the player
releasePlayer()
downStreamUrl = null
waitingLayout!!.text = "Host has stopped \n the live streaming"
waitingLayout!!.visibility = View.VISIBLE
playerView!!.visibility = View.GONE
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
}
private fun initializePlayer() {
}
private fun releasePlayer() {
}
private fun initializePlayer() {
if (player == null) {
dataSourceFactory = DefaultHttpDataSource.Factory()
val mediaSource = HlsMediaSource.Factory(dataSourceFactory!!).createMediaSource(
MediaItem.fromUri(Uri.parse(downStreamUrl))
)
val playerBuilder = ExoPlayer.Builder( /* context = */mContext!!)
player = playerBuilder.build()
// auto play when player is ready
player!!.playWhenReady = startAutoPlay
player!!.setMediaSource(mediaSource)
// if you want display setting for player then remove this line
playerView!!.findViewById<View>(com.google.android.exoplayer2.ui.R.id.exo_settings).visibility =
View.GONE
playerView!!.player = player
}
player!!.prepare()
}
private fun releasePlayer() {
if (player != null) {
player!!.release()
player = null
dataSourceFactory = null
playerView!!.player = null
}
}
override fun onDestroy() {
mContext = null
mActivity = null
downStreamUrl = null
releasePlayer()
if (meeting != null) {
meeting!!.removeAllListeners()
meeting = null
}
super.onDestroy()
}
运行您的应用程序
tadaa!我们的应用已准备好进行直播。容易,不是吗?
在两个不同的设备上安装并运行该应用程序,并确保它们都连接到Internet。
结论
在此博客中,我们熟悉VideosDK以及如何使用视频SDK创建自己的实时流媒体应用程序。
继续创建高级功能,例如屏幕共享,实时消息和其他功能。浏览我们的documentation。
要查看应用程序的完整实现,请查看此GitHub repository。
如果您遇到任何问题或有疑问,请随时加入我们的Discord community。
更多的Android资源
Build an Android Video Calling App — docs
Build an Android Video Calling App using Android Studio and Video SDK — Youtube
videosdk-rtc-android-java-sdk-example
videosdk-rtc-android-kotlin-sdk-example