Agora live Streaming platform is used for analytics tracking and one to many and many to many audio or video live streaming with Agora SDK. In video calling with audio interactive live streaming, users can be host or audience and the host can start live and the audience by joining that streaming.
Agora live Streaming platform is used to integrate SDK in your mobile app which can help you to explore all the features from Agore such as simple-to-use, developer-friendly, customizable, securable, reliable, good quality audio, and video analytics tracking, customer support, video monetization, reduce cost. Kotlin is the preferred language for Android application development. It is a modern programming language that was introduced by JetBrains, the same company behind the popular IntelliJ IDEA IDE, which is widely used for Android development.
1. Register with Agora https://console.agora.io/
2. Activate your project with app id and temporary token.
3. Add the below dependency to your project.
implementation 'io.agora.rtc:full-sdk:x.y.z'
4. Next Step you need to add the below permission in the android manifest
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
The below Screenshot shows how live streaming actually works.
<com.tyt.tytapp.ui.adapter.VideoGridContainer
android:id="@+id/live_video_grid_layout"
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"/>
5. Next step you need to create a live activity by extending another activity name is ActivityRtcLivebase.
You need to pass both side join and live user bellow details compulsory from the agora.
Live user details
Join user details
ActivityLive.kt
appLiveTokenIs =intent.getStringExtra(AppConstants.PREF_USER_APP_TOKEN_INTENT)
channelName = intent.getStringExtra(AppConstants.CHANNEL_MESSAGE)
userId=intent.getStringExtra(AppConstants.PREF_USERS_ID)
experienceId=intent.getIntExtra(AppConstants.PREF_EXPERIENCE_ID, -1)
channelProfile = intent.getIntExtra(AppConstants.PROFILE_MESSAGE,-1)
profileImage=intent.getStringExtra(AppConstants.PREF_USER_PROFILE_LIVE)
profilerName=intent.getStringExtra(AppConstants.PREF_USER_PROFILER_NAME)
uidIs=intent.getLongExtra(AppConstants.UID,0).toInt()
uidClode=intent.getIntExtra(AppConstants.CLOUD_UID,0)
cloudeUrl=intent.getStringExtra(AppConstants.CLOUD_URL)
sid=intent.getStringExtra(AppConstants.CLOUD_SID)
expAddress= intent.getStringExtra(AppConstants.LIVE_ADDRESS)
description= intent.getStringExtra(AppConstants.LIVE_DESCRIPTION)
createDate= intent.getStringExtra(AppConstants.LIVE_EXP_DATE)
screenUid=intent.getStringExtra(AppConstants.UID_SCREEN_SHOT)
if (intent.hasExtra(AppConstants.LIVE_EXPERIENCE_KEY)) {
intent.getParcelableExtra<ModelResponseExperienceDetailsData>(AppConstants.LIVE_EXPERIENCE_KEY)
?.let {
dataExperienceDetails= it
Log.d("jigdata==","live="+dataExperienceDetails)
if(channelProfile==2){
if(!appLiveTokenIs.isNullOrEmpty()){
initUI()
initData()
}
}
}
}
if(channelProfile==1){
if(!appLiveTokenIs.isNullOrEmpty()){
initUI()
initData()
}
}
if(channelProfile==2) {
//toolbar
initToolbar()
baseActivityBinding.baseActivityToolbar.visibility=View.VISIBLE
baseActivityBinding.baseActivityToolbarTextviewTitleCenter.visibility = View.VISIBLE
baseActivityBinding.baseActivityToolbarTextviewTitleCenter.text = resources.getString(R.string.text_title_experience_title)
baseActivityBinding.baseActivityToolbarImageviewBack.visibility = View.VISIBLE
}
if(channelProfile==1)
{
baseActivityBinding.baseActivityToolbar.visibility=View.GONE
}
activityLiveBinding.activityLiveButtonLearnMore.setSafeOnClickListener {
if (wikiPediaKey.isNullOrEmpty()) {
showLocationCoordinatesInvalidDialogForWikiPedia()
}else{
val intent = Intent(this, ActivityWebView::class.java)
intent.putExtra(AppConstants.PREF_KEY_WIKI_PEDIA, wikiPediaKey)
startActivity(intent)
}
}
}
override fun onStop() {
super.onStop()
}
override fun onResume() {
super.onResume()
if(cTimer!=null){
cTimer?.cancel()
}
}
override fun onDestroy() {
super.onDestroy()
if(cTimer!=null){
cTimer?.cancel()
}
}
override fun onPause() {
super.onPause()
startOTPTimer()
}
private fun initUI() {
val roomName: TextView = findViewById(R.id.activity_live_text_view_text_name)
roomName.text =profilerName
if(channelProfile==2)
{
roomName.text = profilerName
}
roomName.isSelected = true
initUserIcon()
val role: Int = intent.getIntExtra(
Constants.KEY_CLIENT_ROLE,
Constants.CLIENT_ROLE_AUDIENCE
)
val isBroadcaster:Boolean = (channelProfile == Constants.CLIENT_ROLE_BROADCASTER)
mMuteVideoBtn = findViewById(R.id.live_btn_mute_video)
mMuteVideoBtn.setOnClickListener() {
onMuteVideoClicked(it)
}
mMuteVideoBtn.isActivated = isBroadcaster
mMuteAudioBtn = findViewById(R.id.live_btn_mute_audio)
mMuteAudioBtn.setOnClickListener() {
onMuteAudioClicked(it)
}
mMuteAudioBtn.isActivated = isBroadcaster
mLiveButton= findViewById(R.id.live_btn_switch_camera)
if(channelProfile==2){
activityLiveBinding.activityLiveBottomLayout.visibility=View.GONE
activityLiveBinding.activitLiveTopLayout.visibility=View.VISIBLE
activityLiveBinding.activityLiveBottomExploreAreaConstraintLayout.visibility=View.VISIBLE
//toolbar
initToolbar()
baseActivityBinding.baseActivityToolbarTextviewTitleCenter.visibility = View.VISIBLE
baseActivityBinding.baseActivityToolbarTextviewTitleCenter.text = resources.getString(R.string.text_title_experience_title)
baseActivityBinding.baseActivityToolbarImageviewBack.visibility = View.VISIBLE
activityLiveBinding.activityLiveTextViewTextLocationDescription.text=dataExperienceDetails.experienceDetails.experienceDescription
activityLiveBinding.activityLiveTextViewTextLocationName.text=dataExperienceDetails.experienceDetails.experienceAddress
activityLiveBinding.activityLiveTextViewTextDate.text = AppDateFormatUtils.convertDate(
dataExperienceDetails.createdAt,
DateFormat.NUMERICAL_REVERSE_DATE_WITH_DASH_AND_TIME_MIDDLE_T,
DateFormat.FULL_DATE_AND_TIME,
true
)
if(dataExperienceDetails.isOriginal){
Glide.with(context)
.load(dataExperienceDetails.owner)
.error(R.drawable.ic_defaultpic)
.placeholder(R.drawable.ic_defaultpic)
.into(activityLiveBinding.liveProfileImage)
}else{
Glide.with(context)
.load(dataExperienceDetails.originalOwner!!.profilePic)
.error(R.drawable.ic_defaultpic)
.placeholder(R.drawable.ic_defaultpic)
.into(activityLiveBinding.liveProfileImage)
}
mMuteAudioBtn.visibility=View.GONE
mMuteVideoBtn.visibility=View.GONE
mLiveButton.visibility=View.GONE
}
if(channelProfile==1){
initToolbar()
baseActivityBinding.baseActivityToolbarTextviewTitleCenter.visibility = View.GONE
baseActivityBinding.baseActivityToolbarImageviewBack.visibility = View.GONE
activityLiveBinding.activitLiveTopLayout.visibility=View.GONE
mMuteAudioBtn.visibility=View.VISIBLE
mMuteVideoBtn.visibility=View.VISIBLE
mLiveButton.visibility=View.VISIBLE
Glide.with(context)
.load(appPreferences.getAppPrefString(AppConstants.PREF_USER_PROFILE_IMAGE))
.error(R.drawable.ic_defaultpic)
.placeholder(R.drawable.ic_defaultpic)
.into(activityLiveBinding.liveProfileImage)
}
mVideoGridContainer = findViewById(R.id.live_video_grid_layout)
mVideoGridContainer!!.setStatsManager(statsManager())
rtcEngine().setClientRole(channelProfile)
if (isBroadcaster) startBroadcast()
}
private fun initUserIcon() {
val origin = BitmapFactory.decodeResource(resources, R.drawable.fake_user_icon)
val drawable = RoundedBitmapDrawableFactory.create(resources, origin)
drawable.isCircular = true
}
private fun initData() {
mVideoDimension =
Constants.VIDEO_DIMENSIONS.get(config().videoDimenIndex)
}
private fun startBroadcast() {
rtcEngine().setClientRole(Constants.CLIENT_ROLE_BROADCASTER)
val surface: SurfaceView = prepareRtcVideo(uidIs, true)
surfaceViewCached = surface
executeAfterSomeTime(5000) {
takeScreenShotAndSend()//07-03-22
}
mVideoGridContainer!!.addUserVideoSurface(uidIs.toInt(), surface, true)
mMuteAudioBtn.isActivated = true//change
}
private fun stopBroadcast() {
rtcEngine().setClientRole(Constants.CLIENT_ROLE_AUDIENCE)
removeRtcVideo(uidIs.toInt(), true)
mVideoGridContainer!!.removeUserVideo(uidIs.toInt(), true)
mMuteAudioBtn.isActivated = true
}
override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
Log.d("==>","onJoinChannelSuccess"+uid)
// Do nothing at the moment
}
override fun onUserJoined(uid: Int, elapsed: Int) {
Log.d("==>","onUserJoined="+uid)
// Do nothing at the moment
}
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread(Runnable { removeRemoteUser(uid)
if (reason == 0)
{
showLiveStreamOffDialog()
}
})
}
override fun onFirstRemoteVideoDecoded(uid: Int, width: Int, height: Int, elapsed: Int) {
runOnUiThread(Runnable { renderRemoteUser(uid) })
}
private fun renderRemoteUser(uid: Int) {
val surface: SurfaceView = prepareRtcVideo(uid, false)
Log.d("==>","surface="+surface)
surFaceIs = surface.toString()
surfaceViewCached = surface
if(channelProfile==2){
surfaceViewCachedJoin=surface
}
mVideoGridContainer!!.addUserVideoSurface(uid, surface, false)
if(channelProfile==2){
surfaceViewCachedJoin?.let { surfaceView ->
LiveSuccess=true
}
}
}
private fun removeRemoteUser(uid: Int) {
removeRtcVideo(uid, false)
//dashboardViewModel.endLiveApi(experienceId, userId)
mVideoGridContainer?.removeUserVideo(uid, false)
}
override fun onLocalVideoStats(stats: LocalVideoStats?) {
Log.d("==>","LiveSuccessF="+LiveSuccess)
if(channelProfile==2){
surfaceViewCachedJoin?.let { surfaceView ->
Log.d("==>","dooo="+surfaceView.toString())
if(surfaceView.toString().isEmpty()){
if (intent.hasExtra(AppConstants.LIVE_EXPERIENCE_KEY)) {
intent.getParcelableExtra<ModelResponseExperienceDetailsData>(AppConstants.LIVE_EXPERIENCE_KEY)
?.let {
dataExperienceDetails= it
initUI()
initData()
joinChannel()
}
}
}
}
}
if(!LiveSuccess){
if (intent.hasExtra(AppConstants.LIVE_EXPERIENCE_KEY)) {
LIVE_EXPERIENCE_KEY)
?.let {
dataExperienceDetails= it
Log.d("==>","dooo3="+intent.hasExtra(AppConstants.LIVE_EXPERIENCE_KEY))
initUI()
initData()
joinChannel()
}
}
}
if (!statsManager()!!.isEnabled) return
val data = statsManager()!!.getStatsData(uidIs.toInt()) as LocalStatsData ?: return
data.width = mVideoDimension.width
data.height = mVideoDimension.height
data.framerate = stats!!.sentFrameRate
}
override fun onBackPressed() {
if(backPress) {
super.onBackPressed()
if (channelProfile == 1) {
dashboardViewModel.endLiveApi(experienceId, userId, fileName)
setResult(RESULT_OK, Intent().putExtra("experienceId",experienceId).putExtra("userId",userId).putExtra("fileName",fileName))
backPress=false
}
}
// finish()
}
override fun onRtcStats(stats: RtcStats?) {
Log.d("==>","onRtcStats="+stats)
if (!statsManager()!!.isEnabled) return
val data = statsManager()!!.getStatsData(uidIs) as LocalStatsData ?: return
data.lastMileDelay = stats!!.lastmileDelay
data.videoSendBitrate = stats.txVideoKBitRate
data.videoRecvBitrate = stats.rxVideoKBitRate
data.audioSendBitrate = stats.txAudioKBitRate
data.audioRecvBitrate = stats.rxAudioKBitRate
data.cpuApp = stats.cpuAppUsage
data.cpuTotal = stats.cpuAppUsage
data.sendLoss = stats.txPacketLossRate
data.recvLoss = stats.rxPacketLossRate
}
override fun onNetworkQuality(uid: Int, txQuality: Int, rxQuality:Int) {
if (!statsManager()!!.isEnabled()) return
val data: StatsData = statsManager()!!.getStatsData(uid) ?: return
data.setSendQuality(statsManager()!!.qualityToString(txQuality))
data.setRecvQuality(statsManager()!!.qualityToString(rxQuality))
}
override fun onRemoteVideoStats(stats: RemoteVideoStats?) {
Log.d("==>","onRemoteVideoStats="+stats)
if (!statsManager()!!.isEnabled()) return
val data: RemoteStatsData = statsManager()!!.getStatsData(stats!!.uid) as RemoteStatsData ?: return
data.setWidth(stats.width)
data.setHeight(stats.height)
data.setFramerate(stats.rendererOutputFrameRate)
data.setVideoDelay(stats.delay)
}
override fun onRemoteAudioStats(stats: RemoteAudioStats?) {
if (!statsManager()!!.isEnabled) return
val data = statsManager()!!.getStatsData(stats!!.uid) as RemoteStatsData
?: return
data.audioNetDelay = stats.networkTransportDelay
data.audioNetJitter = stats.jitterBufferDelay
data.audioLoss = stats.audioLossRate
data.audioQuality = statsManager()!!.qualityToString(stats.quality)
}
//start timer function
fun startOTPTimer() {
cancelTimer()
cTimer = object : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
val minutes: Long = millisUntilFinished / 1000 / 60
val seconds = (millisUntilFinished / 1000 % 60)
}
override fun onFinish() {
if(channelProfile==1) {
dashboardViewModel.endLiveApi(experienceId, userId,fileName)
}
}
}
cTimer?.start()
}
private fun cancelTimer() {
if(cTimer!=null){
cTimer?.cancel()
}
}
override fun finish() {
statsManager()!!.clearAllData()
if(channelProfile==1) {
dashboardViewModel.endLiveApi(experienceId, userId, fileName)
endLiveSuccess=false
appLiveTokenIs=""
channelName=""
dashboardViewModel.booleanGoLiveSuccess.value=false
}
super.finish()
}
fun onLeaveClicked(view: View?) {
// finishLive()
finish()
}
fun onSwitchCameraClicked(view: View?) {
if (view!!.isActivated) {
mMuteVideoBtn.setImageResource(R.drawable.ic_videoofff)
} else {
mMuteVideoBtn.setImageResource(R.drawable.ic_videoonf)
}
rtcEngine().switchCamera()
}
fun onBeautyClicked(view: View) {
view.isActivated = !view.isActivated
rtcEngine().setBeautyEffectOptions(
view.isActivated,
Constants.DEFAULT_BEAUTY_OPTIONS
)
}
fun onMuteAudioClicked(view: View) {
// if (!mMuteVideoBtn.isActivated) return
if (view.isActivated) {
mMuteAudioBtn.setImageResource(R.drawable.ic_mutemikef)
} else {
rtcEngine().enableVideo()// startBroadcast()
mMuteAudioBtn.setImageResource(R.drawable.ic_mikef)
}
rtcEngine().muteLocalAudioStream(view.isActivated)
view.isActivated = !view.isActivated
}
fun onMuteVideoClicked(view: View) {
if (view.isActivated) {
rtcEngine().disableVideo()//stopBroadcast()
mMuteVideoBtn.setImageResource(R.drawable.ic_videoofff)
} else {
rtcEngine().enableVideo()// startBroadcast()
mMuteVideoBtn.setImageResource(R.drawable.ic_videoonf)
}
view.isActivated = !view.isActivated
}
companion object {
private val TAG = LiveActivity::class.java.simpleName
}}}
abstract class ActivityRtcLiveBase : LiveBaseActivity(), EventHandler {
var channelNameis: String? = null
var channelProfile:Int = 0
var uid:Long=0
var temp:Long=0
var appLiveToken: String? = null
private lateinit var dataExperienceDetails: ModelResponseExperienceDetailsData
private var surFaceIs:String?=null
private var liveSuccessFull:Boolean=false
private var surfaceViewJoin:SurfaceView?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerRtcEventHandler(this)
val intent = intent
appLiveToken =intent.getStringExtra(AppConstants.PREF_USER_APP_TOKEN_INTENT)
channelNameis =intent.getStringExtra(AppConstants.CHANNEL_MESSAGE)
channelProfile = intent.getIntExtra(AppConstants.PROFILE_MESSAGE,-1)
uid=intent.getLongExtra(AppConstants.UID,0)
if (intent.hasExtra(AppConstants.LIVE_EXPERIENCE_KEY)) {
intent.getParcelableExtra<ModelResponseExperienceDetailsData>(AppConstants.LIVE_EXPERIENCE_KEY)
?.let {
if(it!=null){
dataExperienceDetails= it
Log.d("jigData==","live="+dataExperienceDetails)
}
}
}
if(!appLiveToken.isNullOrEmpty()){
joinChannel()
}
}
private fun configVideo() {
val configuration = VideoEncoderConfiguration(
Constants.VIDEO_DIMENSIONS.get(config().videoDimenIndex),
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
Log.d("==>","configuration="+configuration)
configuration.mirrorMode = Constants.VIDEO_MIRROR_MODES.get(config().mirrorEncodeIndex)
rtcEngine().setVideoEncoderConfiguration(configuration)
}
open fun joinChannel() {
var token=appLiveToken
if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
token = null // default, no token
}
rtcEngine().setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING)
rtcEngine().enableVideo()
configVideo()
rtcEngine().joinChannel(token,channelNameis, "", uid.toInt())
}
protected fun prepareRtcVideo(uid: Int, local: Boolean): SurfaceView {
val surface = RtcEngine.CreateRendererView(applicationContext)
surFaceIs= surface.toString()
surfaceViewJoin=surface
surfaceViewJoin?.let { surfaceView ->
liveSuccessFull=true
} ?:
if(channelProfile==2){
if(surface==null)
{
joinChannel()
}
}
if(!liveSuccessFull){
if(channelProfile==2){
if(surface==null)
{
joinChannel()
}
}
}
if (local) {
rtcEngine().setupLocalVideo(
VideoCanvas(
surface,
VideoCanvas.RENDER_MODE_HIDDEN,
uid,
Constants.VIDEO_MIRROR_MODES.get(config().mirrorLocalIndex)
)
)
} else {
Log.d("==>","surface="+surface)
rtcEngine().setupRemoteVideo(
VideoCanvas(
surface,
VideoCanvas.RENDER_MODE_HIDDEN,
uid,
Constants.VIDEO_MIRROR_MODES.get(config().mirrorRemoteIndex)
)
)
}
return surface
}
private fun reApiCallOfLive() {
intent.putExtra(AppConstants.LIVE_EXPERIENCE_KEY, dataExperienceDetails)
val channelProfile = io.agora.rtc.Constants.CLIENT_ROLE_AUDIENCE
val intent = Intent(this, LiveActivity::class.java)
val name = dataExperienceDetails.owner.firstName + " " + dataExperienceDetails.owner.lastName
intent.putExtra(
AppConstants.PREF_USER_APP_TOKEN_INTENT,
appPreferences.getAppPrefString(AppConstants.PREF_USER_JOIN_LIVE_APP_TOKEN)
)
intent.putExtra(
AppConstants.CHANNEL_MESSAGE, dataExperienceDetails.experienceDetails.roomName
)
intent.putExtra(
AppConstants.PREF_USER_PROFILE_LIVE,
appPreferences.getAppPrefString(AppConstants.PREF_USER_EXPERIENCE_USER_DETAILS_PROFILE_IMAGE)
)
intent.putExtra(
AppConstants.DATA_EXPERIENCE_DETAILS,
dataExperienceDetails
)
intent.putExtra(AppConstants.PREF_USER_PROFILER_NAME, name)
intent.putExtra(
AppConstants.PROFILE_MESSAGE,
channelProfile
) //Constants.CLIENT_ROLE_BROADCASTER;//for host Constants.CLIENT_ROLE_AUDIENCE;//for audience
intent.putExtra(
AppConstants.UID,
appPreferences.getAppPrefString(AppConstants.PREF_USER_DETAILS_Uid_JOIN_LIVE)!!
.toLongOrNull()
)
intent.putExtra(
AppConstants.LIVE_ADDRESS,
dataExperienceDetails.experienceDetails.experienceAddress
)
intent.putExtra(
AppConstants.LIVE_DESCRIPTION,
dataExperienceDetails.experienceDetails.experienceDescription
)
intent.putExtra(AppConstants.LIVE_EXP_DATE, dataExperienceDetails.createdAt)
shouldDoTopToBottomTransitionOnScreenChange = true
startActivity(intent)
finish()
}
protected fun removeRtcVideo(uid: Int, local: Boolean) {
if (local) {
rtcEngine().setupLocalVideo(null)
} else {
Log.d("==>","localrm="+local)
rtcEngine().setupRemoteVideo(VideoCanvas(null, VideoCanvas.RENDER_MODE_HIDDEN, uid))
}
}
override fun onDestroy() {
super.onDestroy()
removeRtcEventHandler(this)
rtcEngine().leaveChannel()
}
}
abstract class LiveBaseActivity : BaseActivity(), EventHandler {
protected var mDisplayMetrics = DisplayMetrics()
protected var mStatusBarHeight = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setGlobalLayoutListener()
displayMetrics
initStatusBarHeight()
}
private fun setGlobalLayoutListener() {
val layout = findViewById<View>(Window.ID_ANDROID_CONTENT)
val observer = layout.viewTreeObserver
observer.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
layout.viewTreeObserver.removeOnGlobalLayoutListener(this)
onGlobalLayoutCompleted()
}
})
}
protected open fun onGlobalLayoutCompleted() {}
private val displayMetrics: Unit
private get() {
windowManager.defaultDisplay.getMetrics(mDisplayMetrics)
}
private fun initStatusBarHeight() {
mStatusBarHeight = WindowUtil.getSystemStatusBarHeight(this)
}
protected fun application(): MyApplication {
return application as MyApplication
}
protected fun rtcEngine(): RtcEngine {
return application().rtcEngine()!!
}
protected fun config(): EngineConfig {
return application().engineConfig()!!
}
protected fun statsManager(): StatsManager? {
return application().statsManager()
}
protected fun registerRtcEventHandler(handler: EventHandler?) {
application().registerEventHandler(handler)
}
protected fun removeRtcEventHandler(handler: EventHandler?) {
application().removeEventHandler(handler)
}
override fun onFirstRemoteVideoDecoded(uid: Int, width: Int, height: Int, elapsed: Int) {}
override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
}
override fun onLeaveChannel(stats: RtcStats?) {}
override fun onUserOffline(uid: Int, reason: Int) {}
override fun onUserJoined(uid: Int, elapsed: Int) {
}
override fun onLastmileQuality(quality: Int) {}
override fun onLastmileProbeResult(result: LastmileProbeResult?) {}
override fun onLocalVideoStats(stats: LocalVideoStats?) {}
override fun onRtcStats(stats: RtcStats?) {}
override fun onNetworkQuality(uid: Int, txQuality: Int, rxQuality: Int) {}
override fun onRemoteVideoStats(stats: RemoteVideoStats?) {}
override fun onRemoteAudioStats(stats: RemoteAudioStats?) {}
}
public class VideoGridContainer extends RelativeLayout implements Runnable {
private static final int MAX_USER = 4;
private static final int STATS_REFRESH_INTERVAL = 2000;
private static final int STAT_LEFT_MARGIN = 34;
private static final int STAT_TEXT_SIZE = 10;
private SparseArray<ViewGroup> mUserViewList = new SparseArray<>(MAX_USER);
private List<Integer> mUidList = new ArrayList<>(MAX_USER);
private StatsManager mStatsManager;
private Handler mHandler;
private int mStatMarginBottom;
public VideoGridContainer(Context context) {
super(context);
init();
}
public VideoGridContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public VideoGridContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setBackgroundResource(R.drawable.live_room_bg);
mStatMarginBottom = getResources().getDimensionPixelSize(
R.dimen.live_stat_margin_bottom);
mHandler = new Handler(getContext().getMainLooper());
}
public void setStatsManager(StatsManager manager) {
mStatsManager = manager;
}
public void addUserVideoSurface(int uid, SurfaceView surface, boolean isLocal) {
if (surface == null) {
return;
}
int id = -1;
if (isLocal) {
if (mUidList.contains(0)) {
mUidList.remove((Integer) 0);
mUserViewList.remove(0);
}
if (mUidList.size() == MAX_USER) {
mUidList.remove(0);
mUserViewList.remove(0);
}
id = 0;
} else {
if (mUidList.contains(uid)) {
mUidList.remove((Integer) uid);
mUserViewList.remove(uid);
}
if (mUidList.size() < MAX_USER) {
id = uid;
}
}
if (id == 0) mUidList.add(0, uid);
else mUidList.add(uid);
if (id != -1) {
mUserViewList.append(uid, createVideoView(surface));
if (mStatsManager != null) {
mStatsManager.addUserStats(uid, isLocal);
if (mStatsManager.isEnabled()) {
mHandler.removeCallbacks(this);
mHandler.postDelayed(this, STATS_REFRESH_INTERVAL);
}
}
requestGridLayout();
}
}
private ViewGroup createVideoView(SurfaceView surface) {
RelativeLayout layout = new RelativeLayout(getContext());
layout.setId(surface.hashCode());
LayoutParams videoLayoutParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layout.addView(surface, videoLayoutParams);
TextView text = new TextView(getContext());
text.setId(layout.hashCode());
LayoutParams textParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
textParams.bottomMargin = mStatMarginBottom;
textParams.leftMargin = STAT_LEFT_MARGIN;
text.setTextColor(Color.WHITE);
text.setTextSize(STAT_TEXT_SIZE);
layout.addView(text, textParams);
return layout;
}
public void removeUserVideo(int uid, boolean isLocal) {
if (isLocal && mUidList.contains(0)) {
mUidList.remove((Integer) 0);
mUserViewList.remove(0);
} else if (mUidList.contains(uid)) {
mUidList.remove((Integer) uid);
mUserViewList.remove(uid);
}
mStatsManager.removeUserStats(uid);
requestGridLayout();
if (getChildCount() == 0) {
mHandler.removeCallbacks(this);
}
}
private void requestGridLayout() {
removeAllViews();
layout(mUidList.size());
}
private void layout(int size) {
LayoutParams[] params = getParams(size);
for (int i = 0; i < size; i++) {
addView(mUserViewList.get(mUidList.get(i)), params[i]);
}
}
private LayoutParams[] getParams(int size) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
LayoutParams[] array =
new LayoutParams[size];
for (int i = 0; i < size; i++) {
if (i == 0) {
array[0] = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
array[0].addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
array[0].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
} else if (i == 1) {
array[1] = new LayoutParams(width, height / 2);
array[0].height = array[1].height;
array[1].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(0)).getId());
array[1].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
} else if (i == 2) {
array[i] = new LayoutParams(width / 2, height / 2);
array[i - 1].width = array[i].width;
array[i].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(i - 1)).getId());
array[i].addRule(RelativeLayout.ALIGN_TOP, mUserViewList.get(mUidList.get(i - 1)).getId());
} else if (i == 3) {
array[i] = new LayoutParams(width / 2, height / 2);
array[0].width = width / 2;
array[1].addRule(RelativeLayout.BELOW, 0);
array[1].addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
array[1].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(0)).getId());
array[1].addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
array[2].addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
array[2].addRule(RelativeLayout.RIGHT_OF, 0);
array[2].addRule(RelativeLayout.ALIGN_TOP, 0);
array[2].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(0)).getId());
array[3].addRule(RelativeLayout.BELOW, mUserViewList.get(mUidList.get(1)).getId());
array[3].addRule(RelativeLayout.RIGHT_OF, mUserViewList.get(mUidList.get(2)).getId());
}
}
return array;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
clearAllVideo();
}
private void clearAllVideo() {
removeAllViews();
mUserViewList.clear();
mUidList.clear();
mHandler.removeCallbacks(this);
}
@Override
public void run() {
if (mStatsManager != null && mStatsManager.isEnabled()) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
RelativeLayout layout = (RelativeLayout) getChildAt(i);
TextView text = layout.findViewById(layout.hashCode());
if (text != null) {
StatsData data = mStatsManager.getStatsData(mUidList.get(i));
String info = data != null ? data.toString() : null;
if (info != null) text.setText(info);
}
}
mHandler.postDelayed(this, STATS_REFRESH_INTERVAL);
}
}
}
public class AgoraEventHandler extends IRtcEngineEventHandler {
private ArrayList<EventHandler> mHandler = new ArrayList<>();
public void addHandler(EventHandler handler) {
mHandler.add(handler);
}
public void removeHandler(EventHandler handler) {
mHandler.remove(handler);
}
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
for (EventHandler handler : mHandler) {
handler.onJoinChannelSuccess(channel, uid, elapsed);
}
}
@Override
public void onLeaveChannel(RtcStats stats) {
for (EventHandler handler : mHandler) {
handler.onLeaveChannel(stats);
}
}
@Override
public void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) {
for (EventHandler handler : mHandler) {
handler.onFirstRemoteVideoDecoded(uid, width, height, elapsed);
}
}
@Override
public void onUserJoined(int uid, int elapsed) {
for (EventHandler handler : mHandler) {
handler.onUserJoined(uid, elapsed);
}
}
@Override
public void onUserOffline(int uid, int reason) {
for (EventHandler handler : mHandler) {
handler.onUserOffline(uid, reason);
}
}
@Override
public void onLocalVideoStats(LocalVideoStats stats) {
for (EventHandler handler : mHandler) {
handler.onLocalVideoStats(stats);
}
}
@Override
public void onRtcStats(RtcStats stats) {
for (EventHandler handler : mHandler) {
handler.onRtcStats(stats);
}
}
@Override
public void onNetworkQuality(int uid, int txQuality, int rxQuality) {
for (EventHandler handler : mHandler) {
handler.onNetworkQuality(uid, txQuality, rxQuality);
}
}
@Override
public void onRemoteVideoStats(RemoteVideoStats stats) {
for (EventHandler handler : mHandler) {
handler.onRemoteVideoStats(stats);
}
}
@Override
public void onRemoteAudioStats(RemoteAudioStats stats) {
for (EventHandler handler : mHandler) {
handler.onRemoteAudioStats(stats);
}
}
@Override
public void onLastmileQuality(int quality) {
for (EventHandler handler : mHandler) {
handler.onLastmileQuality(quality);
}
}
@Override
public void onLastmileProbeResult(LastmileProbeResult result) {
for (EventHandler handler : mHandler) {
handler.onLastmileProbeResult(result);
}
}
}
public class EngineConfig {
// private static final int DEFAULT_UID = 0;
// private int mUid = DEFAULT_UID;
private String mChannelName;
private boolean mShowVideoStats;
private int mDimenIndex = Constants.DEFAULT_PROFILE_IDX;
private int mMirrorLocalIndex;
private int mMirrorRemoteIndex;
private int mMirrorEncodeIndex;
public int getVideoDimenIndex() {
return mDimenIndex;
}
public void setVideoDimenIndex(int index) {
mDimenIndex = index;
}
public String getChannelName() {
return mChannelName;
}
public void setChannelName(String mChannel) {
this.mChannelName = mChannel;
}
public boolean ifShowVideoStats() {
return mShowVideoStats;
}
public void setIfShowVideoStats(boolean show) {
mShowVideoStats = show;
}
public int getMirrorLocalIndex() {
return mMirrorLocalIndex;
}
public void setMirrorLocalIndex(int index) {
mMirrorLocalIndex = index;
}
public int getMirrorRemoteIndex() {
return mMirrorRemoteIndex;
}
public void setMirrorRemoteIndex(int index) {
mMirrorRemoteIndex = index;
}
public int getMirrorEncodeIndex() {
return mMirrorEncodeIndex;
}
public void setMirrorEncodeIndex(int index) {
mMirrorEncodeIndex = index;
}
}
public interface EventHandler {
void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed);
void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats);
void onJoinChannelSuccess(String channel, int uid, int elapsed);
void onUserOffline(int uid, int reason);
void onUserJoined(int uid, int elapsed);
void onLastmileQuality(int quality);
void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result);
void onLocalVideoStats(IRtcEngineEventHandler.LocalVideoStats stats);
void onRtcStats(IRtcEngineEventHandler.RtcStats stats);
void onNetworkQuality(int uid, int txQuality, int rxQuality);
void onRemoteVideoStats(IRtcEngineEventHandler.RemoteVideoStats stats);
void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats);
}
public class LocalStatsData extends StatsData {
private static final String FORMAT = "Local(%d)\n\n" +
"%dx%d %dfps\n" +
"LastMile delay: %d ms\n" +
"Video tx/rx (kbps): %d/%d\n" +
"Audio tx/rx (kbps): %d/%d\n" +
"CPU: app/total %.1f%%/%.1f%%\n" +
"Quality tx/rx: %s/%s\n" +
"Loss tx/rx: %d%%/%d%%";
private int lastMileDelay;
private int videoSend;
private int videoRecv;
private int audioSend;
private int audioRecv;
private double cpuApp;
private double cpuTotal;
private int sendLoss;
private int recvLoss;
@Override
public String toString() {
return String.format(Locale.getDefault(), FORMAT,
getUid(),
getWidth(), getHeight(), getFramerate(),
getLastMileDelay(),
getVideoSendBitrate(), getVideoRecvBitrate(),
getAudioSendBitrate(), getAudioRecvBitrate(),
getCpuApp(), getCpuTotal(),
getSendQuality(), getRecvQuality(),
getSendLoss(), getRecvLoss());
}
public int getLastMileDelay() {
return lastMileDelay;
}
public void setLastMileDelay(int lastMileDelay) {
this.lastMileDelay = lastMileDelay;
}
public int getVideoSendBitrate() {
return videoSend;
}
public void setVideoSendBitrate(int videoSend) {
this.videoSend = videoSend;
}
public int getVideoRecvBitrate() {
return videoRecv;
}
public void setVideoRecvBitrate(int videoRecv) {
this.videoRecv = videoRecv;
}
public int getAudioSendBitrate() {
return audioSend;
}
public void setAudioSendBitrate(int audioSend) {
this.audioSend = audioSend;
}
public int getAudioRecvBitrate() {
return audioRecv;
}
public void setAudioRecvBitrate(int audioRecv) {
this.audioRecv = audioRecv;
}
public double getCpuApp() {
return cpuApp;
}
public void setCpuApp(double cpuApp) {
this.cpuApp = cpuApp;
}
public double getCpuTotal() {
return cpuTotal;
}
public void setCpuTotal(double cpuTotal) {
this.cpuTotal = cpuTotal;
}
public int getSendLoss() {
return sendLoss;
}
public void setSendLoss(int sendLoss) {
this.sendLoss = sendLoss;
}
public int getRecvLoss() {
return recvLoss;
}
public void setRecvLoss(int recvLoss) {
this.recvLoss = recvLoss;
}
}
public class RemoteStatsData extends StatsData {
private static final String FORMAT = "Remote(%d)\n\n" +
"%dx%d %dfps\n" +
"Quality tx/rx: %s/%s\n" +
"Video delay: %d ms\n" +
"Audio net delay/jitter: %dms/%dms\n" +
"Audio loss/quality: %d%%/%s";
private int videoDelay;
private int audioNetDelay;
private int audioNetJitter;
private int audioLoss;
private String audioQuality;
@Override
public String toString() {
return String.format(Locale.getDefault(), FORMAT,
getUid(),
getWidth(), getHeight(), getFramerate(),
getSendQuality(), getRecvQuality(),
getVideoDelay(),
getAudioNetDelay(), getAudioNetJitter(),
getAudioLoss(), getAudioQuality());
}
public static String getFORMAT() {
return FORMAT;
}
public int getVideoDelay() {
return videoDelay;
}
public void setVideoDelay(int videoDelay) {
this.videoDelay = videoDelay;
}
public int getAudioNetDelay() {
return audioNetDelay;
}
public void setAudioNetDelay(int audioNetDelay) {
this.audioNetDelay = audioNetDelay;
}
public int getAudioNetJitter() {
return audioNetJitter;
}
public void setAudioNetJitter(int audioNetJitter) {
this.audioNetJitter = audioNetJitter;
}
public int getAudioLoss() {
return audioLoss;
}
public void setAudioLoss(int audioLoss) {
this.audioLoss = audioLoss;
}
public String getAudioQuality() {
return audioQuality;
}
public void setAudioQuality(String audioQuality) {
this.audioQuality = audioQuality;
}
}
public class StatsData {
private long uid;
private int width;
private int height;
private int framerate;
private String recvQuality;
private String sendQuality;
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getFramerate() {
return framerate;
}
public void setFramerate(int framerate) {
this.framerate = framerate;
}
public String getRecvQuality() {
return recvQuality;
}
public void setRecvQuality(String recvQuality) {
this.recvQuality = recvQuality;
}
public String getSendQuality() {
return sendQuality;
}
public void setSendQuality(String sendQuality) {
this.sendQuality = sendQuality;
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.agora.rtc.Constants;
public class StatsManager {
private List<Integer> mUidList = new ArrayList<>();
private Map<Integer, StatsData> mDataMap = new HashMap<>();
private boolean mEnable = false;
public void addUserStats(int uid, boolean ifLocal) {
if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
return;
}
StatsData data = ifLocal
? new LocalStatsData()
: new RemoteStatsData();
// in case 32-bit unsigned integer uid is received
data.setUid(uid & 0xFFFFFFFFL);
if (ifLocal) mUidList.add(0, uid);
else mUidList.add(uid);
mDataMap.put(uid, data);
}
public void removeUserStats(int uid) {
if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
mUidList.remove((Integer) uid);
mDataMap.remove(uid);
}
}
public StatsData getStatsData(int uid) {
if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
return mDataMap.get(uid);
} else {
return null;
}
}
public String qualityToString(int quality) {
switch (quality) {
case Constants.QUALITY_EXCELLENT:
return "Exc";
case Constants.QUALITY_GOOD:
return "Good";
case Constants.QUALITY_POOR:
return "Poor";
case Constants.QUALITY_BAD:
return "Bad";
case Constants.QUALITY_VBAD:
return "VBad";
case Constants.QUALITY_DOWN:
return "Down";
default:
return "Unk";
}
}
public void enableStats(boolean enabled) {
mEnable = enabled;
}
public boolean isEnabled() {
return mEnable;
}
public void clearAllData() {
mUidList.clear();
mDataMap.clear();
}
}
Agora live Streaming is used for analytics tracking, customer support, video monetization, and cost-effectiveness. Agora live Streaming is used for analytics tracking and one to many and many to many audio or video live streaming with agora SDK, in a video calling with audio interactive live streaming. users can be host or audiences, the host can start live and the audience joins that streaming one too many and many too many audio or video live streaming also possible.
I would like to conclude this blog is used for Live streaming which improves Ease Convenience and cost-effectiveness, used for analytics tracking, and one to many and many to many audio or Video Live Streaming Using Agora SDK and Live Streaming Internet Video Can Whip Up Online Interaction.
Social media and live streaming platforms have made it easy and effortless for anyone to live stream from their smart devices. With live streaming, building brands from new ideas can make it easier for their senior employees and team leads to communicate and exchange information with the team.
Yes, it is possible you can make it.
Yes, it is possible on both devices.
Sometimes the first time a live stream frame is not initialized because of some token invalid issue etc.
Channel used for transmitting real-time data in the agora
It enables users to build unique live videos.
It enables one-to-many and many-to-many audio or video live streaming.
Diving deep into SwiftUI This blog post drops us into…
Corporate efficiency and customization are vital in today's fast-paced world,…
Flutter Codemagic CI/CD makes your Flutter app build, test, and…
This website uses cookies.