直播模块重构及demo更新
This commit is contained in:
@@ -7,6 +7,8 @@ def sdkGroupId = rootProject.findProperty("sellySdkGroupId") ?: "com.sellycloud"
|
||||
def sdkArtifactId = rootProject.findProperty("sellySdkArtifactId") ?: "sellycloudsdk"
|
||||
def sdkVersion = rootProject.findProperty("sellySdkVersion") ?: "1.0.0"
|
||||
def hasLocalSdk = rootProject.file("SellyCloudSDK").exists()
|
||||
def releaseStorePath = project.rootProject.file(findProperty("MY_STORE_FILE") ?: "release.keystore")
|
||||
def hasReleaseKeystore = releaseStorePath != null && releaseStorePath.exists()
|
||||
|
||||
android {
|
||||
namespace 'com.demo.SellyCloudSDK'
|
||||
@@ -28,17 +30,14 @@ android {
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
def storePath = project.rootProject.file(findProperty("MY_STORE_FILE") ?: "")
|
||||
if (storePath != null && storePath.exists()) {
|
||||
storeFile storePath
|
||||
} else {
|
||||
storeFile project.rootProject.file(findProperty("MY_STORE_FILE") ?: "release.keystore")
|
||||
if (hasReleaseKeystore) {
|
||||
storeFile releaseStorePath
|
||||
storePassword findProperty("MY_STORE_PASSWORD") ?: ""
|
||||
keyAlias findProperty("MY_KEY_ALIAS") ?: ""
|
||||
keyPassword findProperty("MY_KEY_PASSWORD") ?: ""
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
storePassword findProperty("MY_STORE_PASSWORD") ?: ""
|
||||
keyAlias findProperty("MY_KEY_ALIAS") ?: ""
|
||||
keyPassword findProperty("MY_KEY_PASSWORD") ?: ""
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +46,12 @@ android {
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
if (hasReleaseKeystore) {
|
||||
signingConfig signingConfigs.release
|
||||
} else {
|
||||
// Allow local CI/dev builds without a private keystore.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
@@ -74,7 +78,6 @@ dependencies {
|
||||
)
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'com.github.pedroSG94.RootEncoder:library:2.6.6'
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
}
|
||||
|
||||
implementation fileTree(
|
||||
@@ -92,10 +95,18 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
implementation 'androidx.activity:activity-ktx:1.9.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
implementation "io.coil-kt:coil:2.6.0"
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -19,15 +19,19 @@
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="SellyCloudRTC Demo"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".FeatureHubActivity"
|
||||
android:name=".login.LoginActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:screenOrientation="fullSensor">
|
||||
android:theme="@style/Theme.AVDemo.NoActionBar"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -35,10 +39,26 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".live.MainActivity"
|
||||
android:name=".FeatureHubActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:theme="@style/Theme.AVDemo.NoActionBar"
|
||||
android:screenOrientation="fullSensor" />
|
||||
|
||||
<activity
|
||||
android:name=".live.LivePushActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:theme="@style/Theme.AVDemo.NoActionBar"
|
||||
android:parentActivityName=".FeatureHubActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".live.LivePlayActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:theme="@style/Theme.AVDemo.NoActionBar"
|
||||
android:parentActivityName=".FeatureHubActivity" />
|
||||
|
||||
<activity
|
||||
@@ -48,11 +68,6 @@
|
||||
android:screenOrientation="portrait"
|
||||
android:parentActivityName=".FeatureHubActivity" />
|
||||
|
||||
<!-- 新增:多路播放页面 -->
|
||||
<activity
|
||||
android:name=".live.MultiPlayActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".interactive.InteractiveForegroundService"
|
||||
android:exported="false"
|
||||
|
||||
BIN
example/src/main/ic_launcher-playstore.png
Normal file
BIN
example/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 477 KiB |
@@ -1,11 +1,44 @@
|
||||
package com.demo.SellyCloudSDK
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.demo.SellyCloudSDK.avdemo.AvDemoSettings
|
||||
import com.demo.SellyCloudSDK.avdemo.AvDemoSettingsStore
|
||||
import com.demo.SellyCloudSDK.databinding.ActivityFeatureHubBinding
|
||||
import com.demo.SellyCloudSDK.databinding.DialogLivePresetSettingsBinding
|
||||
import com.demo.SellyCloudSDK.databinding.DialogPlayConfigOverlayBinding
|
||||
import com.demo.SellyCloudSDK.databinding.DialogPushProtocolSheetBinding
|
||||
import com.demo.SellyCloudSDK.interactive.InteractiveLiveActivity
|
||||
import com.demo.SellyCloudSDK.live.MainActivity
|
||||
import com.demo.SellyCloudSDK.live.LivePlayActivity
|
||||
import com.demo.SellyCloudSDK.live.LivePushActivity
|
||||
import com.demo.SellyCloudSDK.live.env.LiveEnvSettings
|
||||
import com.demo.SellyCloudSDK.live.env.LiveEnvSettingsStore
|
||||
import com.demo.SellyCloudSDK.live.env.normalizedAppName
|
||||
import com.demo.SellyCloudSDK.live.env.normalizedVhost
|
||||
import com.demo.SellyCloudSDK.live.square.AliveListRepository
|
||||
import com.demo.SellyCloudSDK.live.square.AliveListResult
|
||||
import com.demo.SellyCloudSDK.live.square.AliveStreamAdapter
|
||||
import com.demo.SellyCloudSDK.live.square.AliveStreamItem
|
||||
import com.demo.SellyCloudSDK.login.DemoLoginStore
|
||||
import com.demo.SellyCloudSDK.login.LoginActivity
|
||||
import com.sellycloud.sellycloudsdk.SellyLiveMode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Entry screen displaying available demo experiences.
|
||||
@@ -13,20 +46,471 @@ import com.demo.SellyCloudSDK.live.MainActivity
|
||||
class FeatureHubActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityFeatureHubBinding
|
||||
private lateinit var settingsStore: AvDemoSettingsStore
|
||||
private lateinit var envStore: LiveEnvSettingsStore
|
||||
private lateinit var loginStore: DemoLoginStore
|
||||
|
||||
private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
|
||||
private lateinit var aliveAdapter: AliveStreamAdapter
|
||||
private val allAliveItems: MutableList<AliveStreamItem> = mutableListOf()
|
||||
private var currentPage = 0
|
||||
private var isPaging = false
|
||||
|
||||
private var selectedTab: Tab = Tab.HOME
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
loginStore = DemoLoginStore(this)
|
||||
if (!loginStore.isLoggedIn()) {
|
||||
redirectToLogin()
|
||||
return
|
||||
}
|
||||
binding = ActivityFeatureHubBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.title = "SellyCloud SDK DEMO"
|
||||
supportActionBar?.hide()
|
||||
settingsStore = AvDemoSettingsStore(this)
|
||||
envStore = LiveEnvSettingsStore(this)
|
||||
selectedTab = savedInstanceState?.getString(KEY_SELECTED_TAB)
|
||||
?.let { runCatching { Tab.valueOf(it) }.getOrNull() }
|
||||
?: Tab.HOME
|
||||
|
||||
binding.cardLiveStreaming.setOnClickListener {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
restoreSettingsToUi()
|
||||
binding.etCallChannelId.setText(getString(R.string.default_call_id))
|
||||
|
||||
binding.cardInteractiveLive.setOnClickListener {
|
||||
startActivity(Intent(this, InteractiveLiveActivity::class.java))
|
||||
setupTabs()
|
||||
setupActions()
|
||||
setupSettingsSave()
|
||||
setupLogout()
|
||||
setupHomeList()
|
||||
refreshAliveList()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!loginStore.isLoggedIn() && !isFinishing) {
|
||||
redirectToLogin()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
uiScope.cancel()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString(KEY_SELECTED_TAB, selectedTab.name)
|
||||
}
|
||||
|
||||
private fun setupTabs() {
|
||||
binding.tabHome.setOnClickListener { selectTab(Tab.HOME) }
|
||||
binding.tabCall.setOnClickListener { selectTab(Tab.CALL) }
|
||||
binding.tabSettings.setOnClickListener { selectTab(Tab.SETTINGS) }
|
||||
selectTab(selectedTab)
|
||||
}
|
||||
|
||||
private fun setupActions() {
|
||||
binding.btnHomeLivePush.setOnClickListener {
|
||||
showPushSettingsDialog { showPushProtocolSheet() }
|
||||
}
|
||||
binding.btnHomeLivePull.setOnClickListener {
|
||||
showPlayConfigDialog()
|
||||
}
|
||||
binding.btnCallSingleChat.setOnClickListener {
|
||||
startInteractive(InteractiveLiveActivity.DEFAULT_CALL_TYPE_P2P)
|
||||
}
|
||||
binding.btnCallConference.setOnClickListener {
|
||||
startInteractive(InteractiveLiveActivity.DEFAULT_CALL_TYPE_GROUP)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHomeList() {
|
||||
aliveAdapter = AliveStreamAdapter { item -> handleAliveItemClick(item) }
|
||||
val layoutManager = GridLayoutManager(this, 2)
|
||||
binding.rvLiveSquare.layoutManager = layoutManager
|
||||
binding.rvLiveSquare.adapter = aliveAdapter
|
||||
binding.rvLiveSquare.addItemDecoration(GridSpacingItemDecoration(2, dp(6)))
|
||||
binding.rvLiveSquare.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (dy <= 0) return
|
||||
val lastVisible = layoutManager.findLastVisibleItemPosition()
|
||||
if (lastVisible >= aliveAdapter.itemCount - 2) {
|
||||
appendNextPage()
|
||||
}
|
||||
}
|
||||
})
|
||||
binding.swipeHome.setOnRefreshListener { refreshAliveList() }
|
||||
}
|
||||
|
||||
private fun refreshAliveList() {
|
||||
binding.swipeHome.isRefreshing = true
|
||||
uiScope.launch {
|
||||
when (val result = AliveListRepository.fetchAliveList()) {
|
||||
is AliveListResult.Success -> {
|
||||
binding.swipeHome.isRefreshing = false
|
||||
val response = result.response
|
||||
if (!response.success) {
|
||||
showHomeError(response.message ?: getString(R.string.home_live_square_error))
|
||||
return@launch
|
||||
}
|
||||
resetPaging(response.list)
|
||||
}
|
||||
is AliveListResult.Error -> {
|
||||
binding.swipeHome.isRefreshing = false
|
||||
showHomeError(result.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPaging(items: List<AliveStreamItem>) {
|
||||
allAliveItems.clear()
|
||||
allAliveItems.addAll(items)
|
||||
currentPage = 0
|
||||
aliveAdapter.replaceAll(emptyList())
|
||||
appendNextPage()
|
||||
updateHomeEmptyState()
|
||||
}
|
||||
|
||||
private fun appendNextPage() {
|
||||
if (isPaging) return
|
||||
val start = currentPage * PAGE_SIZE
|
||||
if (start >= allAliveItems.size) return
|
||||
isPaging = true
|
||||
val end = min(start + PAGE_SIZE, allAliveItems.size)
|
||||
aliveAdapter.appendItems(allAliveItems.subList(start, end))
|
||||
currentPage += 1
|
||||
isPaging = false
|
||||
}
|
||||
|
||||
private fun updateHomeEmptyState() {
|
||||
val empty = allAliveItems.isEmpty()
|
||||
binding.tvHomeEmpty.text = getString(R.string.home_live_square_empty)
|
||||
binding.tvHomeEmpty.visibility = if (empty) View.VISIBLE else View.GONE
|
||||
binding.rvLiveSquare.visibility = if (empty) View.INVISIBLE else View.VISIBLE
|
||||
}
|
||||
|
||||
private fun showHomeError(message: String) {
|
||||
allAliveItems.clear()
|
||||
aliveAdapter.replaceAll(emptyList())
|
||||
binding.tvHomeEmpty.text = message
|
||||
binding.tvHomeEmpty.visibility = View.VISIBLE
|
||||
binding.rvLiveSquare.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
private fun handleAliveItemClick(item: AliveStreamItem) {
|
||||
val liveMode = resolvePlayMode(item.playProtocol)
|
||||
val params = buildPlayParams(item)
|
||||
if (params == null) {
|
||||
Toast.makeText(this, "播放地址缺失", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
startActivity(
|
||||
LivePlayActivity.createIntentWithParams(
|
||||
this,
|
||||
liveMode,
|
||||
params.vhost,
|
||||
params.appName,
|
||||
params.streamName,
|
||||
autoStart = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolvePlayMode(raw: String?): SellyLiveMode {
|
||||
val normalized = raw?.trim()?.uppercase() ?: ""
|
||||
return if (normalized == "WHEP" || normalized == "WHIP" || normalized == "RTC") {
|
||||
SellyLiveMode.RTC
|
||||
} else {
|
||||
SellyLiveMode.RTMP
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPlayParams(item: AliveStreamItem): PlayParams? {
|
||||
val env = envStore.read()
|
||||
val vhost = item.vhost?.trim().orEmpty().ifBlank { env.normalizedVhost() }
|
||||
val app = item.app?.trim().orEmpty().ifBlank { env.normalizedAppName() }
|
||||
val stream = item.stream?.trim().orEmpty()
|
||||
if (vhost.isBlank() || app.isBlank() || stream.isBlank()) return null
|
||||
return PlayParams(vhost = vhost, appName = app, streamName = stream)
|
||||
}
|
||||
|
||||
private data class PlayParams(
|
||||
val vhost: String,
|
||||
val appName: String,
|
||||
val streamName: String
|
||||
)
|
||||
|
||||
private fun startInteractive(defaultCallType: String) {
|
||||
val callId = binding.etCallChannelId.text?.toString()?.trim().orEmpty()
|
||||
if (callId.isEmpty()) {
|
||||
Toast.makeText(this, getString(R.string.call_id_required), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
startActivity(
|
||||
Intent(this, InteractiveLiveActivity::class.java)
|
||||
.putExtra(InteractiveLiveActivity.EXTRA_DEFAULT_CALL_TYPE, defaultCallType)
|
||||
.putExtra(InteractiveLiveActivity.EXTRA_CALL_ID, callId)
|
||||
)
|
||||
}
|
||||
|
||||
private fun showPushProtocolSheet() {
|
||||
val dialog = Dialog(this, R.style.Theme_AVDemo_Dialog_BottomSheet)
|
||||
val sheetBinding = DialogPushProtocolSheetBinding.inflate(layoutInflater)
|
||||
dialog.setContentView(sheetBinding.root)
|
||||
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
dialog.window?.setGravity(Gravity.BOTTOM)
|
||||
|
||||
sheetBinding.btnRtc.setOnClickListener {
|
||||
dialog.dismiss()
|
||||
startActivity(LivePushActivity.createIntent(this, SellyLiveMode.RTC))
|
||||
}
|
||||
sheetBinding.btnRtmp.setOnClickListener {
|
||||
dialog.dismiss()
|
||||
startActivity(LivePushActivity.createIntent(this, SellyLiveMode.RTMP))
|
||||
}
|
||||
sheetBinding.btnCancel.setOnClickListener { dialog.dismiss() }
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun showPushSettingsDialog(onApplied: () -> Unit) {
|
||||
val dialog = Dialog(this)
|
||||
val dialogBinding = DialogLivePresetSettingsBinding.inflate(layoutInflater)
|
||||
dialog.setContentView(dialogBinding.root)
|
||||
dialog.window?.setBackgroundDrawable(android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT))
|
||||
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
dialog.window?.decorView?.setPadding(0, 0, 0, 0)
|
||||
|
||||
val current = settingsStore.read()
|
||||
dialogBinding.etStreamId.setText(generateRandomStreamId())
|
||||
dialogBinding.etFps.setText(current.fps.toString())
|
||||
dialogBinding.etMaxBitrate.setText(current.maxBitrateKbps.toString())
|
||||
dialogBinding.etMinBitrate.setText(current.minBitrateKbps.toString())
|
||||
dialogBinding.rgResolution.check(
|
||||
when (current.resolution) {
|
||||
AvDemoSettings.Resolution.P360 -> R.id.rbRes360p
|
||||
AvDemoSettings.Resolution.P480 -> R.id.rbRes480p
|
||||
AvDemoSettings.Resolution.P540 -> R.id.rbRes540p
|
||||
AvDemoSettings.Resolution.P720 -> R.id.rbRes720p
|
||||
}
|
||||
)
|
||||
|
||||
dialogBinding.btnClose.setOnClickListener { dialog.dismiss() }
|
||||
dialogBinding.btnApply.setOnClickListener {
|
||||
val streamId = dialogBinding.etStreamId.text?.toString()?.trim().orEmpty()
|
||||
val fps = dialogBinding.etFps.text?.toString()?.trim()?.toIntOrNull()
|
||||
val maxKbps = dialogBinding.etMaxBitrate.text?.toString()?.trim()?.toIntOrNull()
|
||||
val minKbps = dialogBinding.etMinBitrate.text?.toString()?.trim()?.toIntOrNull()
|
||||
|
||||
if (streamId.isEmpty()) {
|
||||
Toast.makeText(this, "请输入 Stream ID", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (fps == null || fps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的 FPS", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (maxKbps == null || maxKbps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的最大码率", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (minKbps == null || minKbps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的最小码率", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val res = when (dialogBinding.rgResolution.checkedRadioButtonId) {
|
||||
R.id.rbRes360p -> AvDemoSettings.Resolution.P360
|
||||
R.id.rbRes480p -> AvDemoSettings.Resolution.P480
|
||||
R.id.rbRes540p -> AvDemoSettings.Resolution.P540
|
||||
else -> AvDemoSettings.Resolution.P720
|
||||
}
|
||||
|
||||
val updated = current.copy(
|
||||
streamId = streamId,
|
||||
resolution = res,
|
||||
fps = fps,
|
||||
maxBitrateKbps = maxKbps,
|
||||
minBitrateKbps = minKbps
|
||||
)
|
||||
settingsStore.write(updated)
|
||||
dialog.dismiss()
|
||||
onApplied()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun showPlayConfigDialog() {
|
||||
val dialog = Dialog(this, R.style.Theme_AVDemo_Dialog_FullscreenOverlay)
|
||||
val dialogBinding = DialogPlayConfigOverlayBinding.inflate(layoutInflater)
|
||||
dialog.setContentView(dialogBinding.root)
|
||||
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
dialog.setCancelable(true)
|
||||
|
||||
dialogBinding.btnClose.setOnClickListener { dialog.dismiss() }
|
||||
dialogBinding.btnStartPlay.setOnClickListener {
|
||||
val input = dialogBinding.etStreamIdOrUrl.text?.toString()?.trim().orEmpty()
|
||||
if (input.isEmpty()) {
|
||||
Toast.makeText(this, "请输入 Stream ID 或 URL", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
val liveMode = if (dialogBinding.rbRtc.isChecked) {
|
||||
SellyLiveMode.RTC
|
||||
} else {
|
||||
SellyLiveMode.RTMP
|
||||
}
|
||||
dialog.dismiss()
|
||||
startActivity(LivePlayActivity.createIntent(this, liveMode, input, autoStart = true))
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun setupSettingsSave() {
|
||||
binding.btnSaveSettings.setOnClickListener {
|
||||
val settings = uiToSettingsOrNull() ?: return@setOnClickListener
|
||||
settingsStore.write(settings)
|
||||
val envSettings = uiToEnvSettings(envStore.read(), settings.streamId)
|
||||
envStore.write(envSettings)
|
||||
Toast.makeText(this, "已保存", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLogout() {
|
||||
binding.btnLogout.setOnClickListener {
|
||||
loginStore.clear()
|
||||
redirectToLogin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun redirectToLogin() {
|
||||
startActivity(LoginActivity.createIntent(this, clearTask = true))
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun restoreSettingsToUi() {
|
||||
val settings = settingsStore.read()
|
||||
binding.etSettingsStreamId.setText(settings.streamId)
|
||||
binding.etSettingsFps.setText(settings.fps.toString())
|
||||
binding.etSettingsMaxBitrate.setText(settings.maxBitrateKbps.toString())
|
||||
binding.etSettingsMinBitrate.setText(settings.minBitrateKbps.toString())
|
||||
when (settings.resolution) {
|
||||
AvDemoSettings.Resolution.P360 -> binding.rgSettingsResolution.check(R.id.rbSettingsRes360p)
|
||||
AvDemoSettings.Resolution.P480 -> binding.rgSettingsResolution.check(R.id.rbSettingsRes480p)
|
||||
AvDemoSettings.Resolution.P540 -> binding.rgSettingsResolution.check(R.id.rbSettingsRes540p)
|
||||
AvDemoSettings.Resolution.P720 -> binding.rgSettingsResolution.check(R.id.rbSettingsRes720p)
|
||||
}
|
||||
restoreEnvSettingsToUi()
|
||||
}
|
||||
|
||||
private fun restoreEnvSettingsToUi() {
|
||||
val env = envStore.read()
|
||||
binding.etSettingsVhost.setText(env.vhost)
|
||||
binding.etSettingsVhostKey.setText(env.vhostKey)
|
||||
binding.etSettingsAppId.setText(env.appId)
|
||||
}
|
||||
|
||||
private fun uiToSettingsOrNull(): AvDemoSettings? {
|
||||
val streamId = binding.etSettingsStreamId.text?.toString()?.trim().orEmpty()
|
||||
val fps = binding.etSettingsFps.text?.toString()?.trim()?.toIntOrNull()
|
||||
val maxKbps = binding.etSettingsMaxBitrate.text?.toString()?.trim()?.toIntOrNull()
|
||||
val minKbps = binding.etSettingsMinBitrate.text?.toString()?.trim()?.toIntOrNull()
|
||||
|
||||
if (streamId.isEmpty()) {
|
||||
Toast.makeText(this, "请输入 Stream ID", Toast.LENGTH_SHORT).show()
|
||||
return null
|
||||
}
|
||||
if (fps == null || fps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的 FPS", Toast.LENGTH_SHORT).show()
|
||||
return null
|
||||
}
|
||||
if (maxKbps == null || maxKbps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的最大码率", Toast.LENGTH_SHORT).show()
|
||||
return null
|
||||
}
|
||||
if (minKbps == null || minKbps <= 0) {
|
||||
Toast.makeText(this, "请输入正确的最小码率", Toast.LENGTH_SHORT).show()
|
||||
return null
|
||||
}
|
||||
|
||||
val res = when (binding.rgSettingsResolution.checkedRadioButtonId) {
|
||||
R.id.rbSettingsRes360p -> AvDemoSettings.Resolution.P360
|
||||
R.id.rbSettingsRes480p -> AvDemoSettings.Resolution.P480
|
||||
R.id.rbSettingsRes540p -> AvDemoSettings.Resolution.P540
|
||||
else -> AvDemoSettings.Resolution.P720
|
||||
}
|
||||
val current = settingsStore.read()
|
||||
return current.copy(
|
||||
streamId = streamId,
|
||||
resolution = res,
|
||||
fps = fps,
|
||||
maxBitrateKbps = maxKbps,
|
||||
minBitrateKbps = minKbps
|
||||
)
|
||||
}
|
||||
|
||||
private fun uiToEnvSettings(current: LiveEnvSettings, streamId: String): LiveEnvSettings {
|
||||
val vhost = binding.etSettingsVhost.text?.toString()?.trim().orEmpty()
|
||||
val vhostKey = binding.etSettingsVhostKey.text?.toString()?.trim().orEmpty()
|
||||
val appId = binding.etSettingsAppId.text?.toString()?.trim().orEmpty()
|
||||
return current.copy(
|
||||
vhost = vhost,
|
||||
vhostKey = vhostKey,
|
||||
appId = appId,
|
||||
defaultStreamId = streamId
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectTab(tab: Tab) {
|
||||
selectedTab = tab
|
||||
binding.tabHome.isSelected = tab == Tab.HOME
|
||||
binding.tabCall.isSelected = tab == Tab.CALL
|
||||
binding.tabSettings.isSelected = tab == Tab.SETTINGS
|
||||
|
||||
binding.pageHome.visibility = if (tab == Tab.HOME) View.VISIBLE else View.GONE
|
||||
binding.pageCall.visibility = if (tab == Tab.CALL) View.VISIBLE else View.GONE
|
||||
binding.pageSettings.visibility = if (tab == Tab.SETTINGS) View.VISIBLE else View.GONE
|
||||
|
||||
val titleRes = when (tab) {
|
||||
Tab.HOME -> R.string.tab_home
|
||||
Tab.CALL -> R.string.tab_call
|
||||
Tab.SETTINGS -> R.string.tab_settings
|
||||
}
|
||||
binding.tvTitle.setText(titleRes)
|
||||
}
|
||||
|
||||
private fun dp(value: Int): Int = (value * resources.displayMetrics.density).roundToInt()
|
||||
|
||||
private fun generateRandomStreamId(): String = Random.nextInt(100, 1000).toString()
|
||||
|
||||
private class GridSpacingItemDecoration(
|
||||
private val spanCount: Int,
|
||||
private val spacing: Int
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
val column = position % spanCount
|
||||
outRect.left = spacing - column * spacing / spanCount
|
||||
outRect.right = (column + 1) * spacing / spanCount
|
||||
outRect.top = if (position < spanCount) spacing else spacing / 2
|
||||
outRect.bottom = spacing
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Tab { HOME, CALL, SETTINGS }
|
||||
|
||||
private companion object {
|
||||
private const val KEY_SELECTED_TAB = "selected_tab"
|
||||
private const val PAGE_SIZE = 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +67,17 @@ class FUBeautyFilterRender(
|
||||
previewWidth: Int,
|
||||
previewHeight: Int
|
||||
) {
|
||||
// GL 上下文可能重建:确保滤镜和 FaceUnity 资源重新初始化
|
||||
isInitialized = false
|
||||
program = -1
|
||||
// 先保存 ApplicationContext,避免 super.initGl 内部触发 initGlFilter 时为空
|
||||
this.appContext = context.applicationContext
|
||||
super.initGl(width, height, context, previewWidth, previewHeight)
|
||||
// 确保使用 ApplicationContext,避免Activity依赖
|
||||
this.appContext = context.applicationContext
|
||||
frameW = width
|
||||
frameH = height
|
||||
// 刷新 FaceUnity GL 资源绑定到新的上下文
|
||||
fuRenderer.reinitializeGlContextBlocking()
|
||||
Log.d(TAG, "initGl: width=$width, height=$height, context=${context.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
@@ -234,6 +240,7 @@ class FUBeautyFilterRender(
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
isInitialized = false
|
||||
if (program != -1) {
|
||||
GLES20.glDeleteProgram(program)
|
||||
program = -1
|
||||
|
||||
@@ -19,7 +19,9 @@ import com.faceunity.wrapper.faceunity
|
||||
import com.pedro.encoder.input.video.CameraHelper
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
/**
|
||||
@@ -50,7 +52,11 @@ class FURenderer(private val context: Context) {
|
||||
private val BUNDLE_AI_HUMAN = "model" + File.separator + "ai_human_processor.bundle"
|
||||
private val BUNDLE_FACE_BEAUTY = "graphics" + File.separator + "face_beautification.bundle"
|
||||
|
||||
private val workerThread = Executors.newSingleThreadExecutor()
|
||||
@Volatile
|
||||
private var workerThreadRef: Thread? = null
|
||||
private val workerThread = Executors.newSingleThreadExecutor { task ->
|
||||
Thread(task, "FURenderer-Worker").also { workerThreadRef = it }
|
||||
}
|
||||
|
||||
// 添加摄像头朝向管理
|
||||
private var currentCameraFacing: CameraHelper.Facing = CameraHelper.Facing.BACK
|
||||
@@ -155,11 +161,6 @@ class FURenderer(private val context: Context) {
|
||||
|
||||
// 检查 SDK 和 GL 是否就绪
|
||||
if (!isAuthSuccess || !isGlInitialized || fuRenderKit == null) {
|
||||
// 如果认证成功但 GL 未初始化,尝试初始化
|
||||
if (isAuthSuccess && !isGlInitialized) {
|
||||
Log.w(TAG, "GL not initialized, attempting to initialize")
|
||||
reinitializeGlContext()
|
||||
}
|
||||
return inputTex
|
||||
}
|
||||
|
||||
@@ -255,30 +256,58 @@ class FURenderer(private val context: Context) {
|
||||
*/
|
||||
fun reinitializeGlContext() {
|
||||
if (!isAuthSuccess) return
|
||||
workerThread.execute { doReinitializeGlContext() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新初始化 GL 上下文(同步等待完成,用于避免美颜空窗)
|
||||
*/
|
||||
fun reinitializeGlContextBlocking(timeoutMs: Long = 2000L) {
|
||||
if (!isAuthSuccess) return
|
||||
if (Thread.currentThread() === workerThreadRef) {
|
||||
doReinitializeGlContext()
|
||||
return
|
||||
}
|
||||
val latch = CountDownLatch(1)
|
||||
workerThread.execute {
|
||||
try {
|
||||
Log.d(TAG, "Reinitializing GL context after protocol switch")
|
||||
|
||||
// 重新获取 FURenderKit 实例(绑定到新的 GL 上下文)
|
||||
fuRenderKit = FURenderKit.getInstance()
|
||||
|
||||
// 重新设置异步纹理模式
|
||||
faceunity.fuSetUseTexAsync(1)
|
||||
|
||||
// 如果之前有美颜配置,重新应用
|
||||
if (faceBeauty != null) {
|
||||
fuRenderKit?.faceBeauty = faceBeauty
|
||||
Log.d(TAG, "Beauty configuration reapplied")
|
||||
}
|
||||
|
||||
isGlInitialized = true
|
||||
Log.d(TAG, "GL context reinitialized successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error reinitializing GL context", e)
|
||||
isGlInitialized = false
|
||||
doReinitializeGlContext()
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "GL context reinit timeout: ${timeoutMs}ms")
|
||||
}
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
Log.w(TAG, "GL context reinit interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
private fun doReinitializeGlContext() {
|
||||
try {
|
||||
Log.d(TAG, "Reinitializing GL context after protocol switch")
|
||||
|
||||
// 重新获取 FURenderKit 实例(绑定到新的 GL 上下文)
|
||||
fuRenderKit = FURenderKit.getInstance()
|
||||
|
||||
// 重新设置异步纹理模式
|
||||
faceunity.fuSetUseTexAsync(1)
|
||||
|
||||
// 如果之前有美颜配置,重新应用
|
||||
if (faceBeauty != null) {
|
||||
fuRenderKit?.faceBeauty = faceBeauty
|
||||
Log.d(TAG, "Beauty configuration reapplied")
|
||||
}
|
||||
|
||||
isGlInitialized = true
|
||||
Log.d(TAG, "GL context reinitialized successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error reinitializing GL context", e)
|
||||
isGlInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.demo.SellyCloudSDK.beauty
|
||||
|
||||
import android.util.Log
|
||||
import com.sellycloud.sellycloudsdk.VideoFrameInterceptor
|
||||
import com.faceunity.core.entity.FURenderInputData
|
||||
import com.faceunity.core.enumeration.CameraFacingEnum
|
||||
import com.faceunity.core.enumeration.FUExternalInputEnum
|
||||
import com.faceunity.core.enumeration.FUInputBufferEnum
|
||||
import com.faceunity.core.enumeration.FUTransformMatrixEnum
|
||||
import com.sellycloud.sellycloudsdk.VideoFrameInterceptor
|
||||
import org.webrtc.JavaI420Buffer
|
||||
import org.webrtc.VideoFrame
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.demo.SellyCloudSDK.R
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,8 @@ import com.sellycloud.sellycloudsdk.interactive.InteractiveChannelMediaOptions
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveConnectionState
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveRtcEngine
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveRtcEngine.ConnectionReason
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveRtcEngineEventHandler
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveRtcEngineConfig
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveRtcEngineEventHandler
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveStreamStats
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveVideoCanvas
|
||||
import com.sellycloud.sellycloudsdk.interactive.InteractiveVideoEncoderConfig
|
||||
@@ -37,6 +37,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityInteractiveLiveBinding
|
||||
|
||||
private var rtcEngine: InteractiveRtcEngine? = null
|
||||
private var lockedCallType: CallType? = null
|
||||
private var localRenderer: SurfaceViewRenderer? = null
|
||||
private lateinit var localSlot: VideoSlot
|
||||
private lateinit var remoteSlots: List<VideoSlot>
|
||||
@@ -106,6 +107,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
setupVideoSlots()
|
||||
initRtcEngine()
|
||||
setupUiDefaults()
|
||||
applyDefaultCallTypeFromIntent()
|
||||
setupControlButtons()
|
||||
|
||||
binding.btnJoin.setOnClickListener {
|
||||
@@ -128,6 +130,24 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyDefaultCallTypeFromIntent() {
|
||||
lockedCallType = when (intent.getStringExtra(EXTRA_DEFAULT_CALL_TYPE)) {
|
||||
DEFAULT_CALL_TYPE_GROUP -> CallType.GROUP
|
||||
DEFAULT_CALL_TYPE_P2P -> CallType.ONE_TO_ONE
|
||||
else -> null
|
||||
}
|
||||
|
||||
when (lockedCallType) {
|
||||
CallType.GROUP -> binding.rbCallTypeGroup.isChecked = true
|
||||
CallType.ONE_TO_ONE -> binding.rbCallTypeP2p.isChecked = true
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
// Home 已经确定单聊/多方通话时,这里不再让用户二次选择。
|
||||
val locked = lockedCallType != null
|
||||
binding.callTypeGroup.isVisible = !locked
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
rtcEngine?.setCaptureVideoFrameInterceptor(null)
|
||||
@@ -151,6 +171,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
private fun initRtcEngine() {
|
||||
val appId = getString(R.string.signaling_app_id)
|
||||
val token = getString(R.string.signaling_token).takeIf { it.isNotBlank() }
|
||||
val kiwiRsName = getString(R.string.signaling_kiwi_rsname).trim()
|
||||
beautyRenderer = FURenderer(this).also { it.setup() }
|
||||
fuFrameInterceptor = beautyRenderer?.let { FuVideoFrameInterceptor(it).apply {
|
||||
setFrontCamera(isFrontCamera)
|
||||
@@ -160,12 +181,13 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
InteractiveRtcEngineConfig(
|
||||
context = applicationContext,
|
||||
appId = appId,
|
||||
defaultToken = token
|
||||
defaultToken = token,
|
||||
kiwiRsName = kiwiRsName
|
||||
)
|
||||
).apply {
|
||||
setEventHandler(rtcEventHandler)
|
||||
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
|
||||
// setVideoEncoderConfiguration(InteractiveVideoEncoderConfig()) 使用默认值
|
||||
// setVideoEncoderConfiguration(InteractiveVideoEncoderConfig()) 使用默认值
|
||||
setVideoEncoderConfiguration(InteractiveVideoEncoderConfig(640, 480 , fps = 20, minBitrateKbps = 150, maxBitrateKbps = 850))
|
||||
setDefaultAudioRoutetoSpeakerphone(true)
|
||||
setCaptureVideoFrameInterceptor { frame ->
|
||||
@@ -318,7 +340,12 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setupUiDefaults() {
|
||||
binding.etCallId.setText(getString(R.string.default_call_id))
|
||||
val presetCallId = intent.getStringExtra(EXTRA_CALL_ID)
|
||||
if (!presetCallId.isNullOrBlank()) {
|
||||
binding.etCallId.setText(presetCallId)
|
||||
} else {
|
||||
binding.etCallId.setText(getString(R.string.default_call_id))
|
||||
}
|
||||
val defaultUser = String.format(
|
||||
getString(R.string.default_user_id),
|
||||
System.currentTimeMillis().toString().takeLast(4)
|
||||
@@ -494,9 +521,9 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
Toast.makeText(this, R.string.signaling_app_id_missing, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
val options = InteractiveChannelMediaOptions(
|
||||
callType = if (binding.rbCallTypeP2p.isChecked) CallType.ONE_TO_ONE else CallType.GROUP
|
||||
)
|
||||
val options = InteractiveChannelMediaOptions(callType = lockedCallType ?: run {
|
||||
if (binding.rbCallTypeP2p.isChecked) CallType.ONE_TO_ONE else CallType.GROUP
|
||||
})
|
||||
val tokenBundle = buildToken(appId, callId, userInput) ?: return
|
||||
pendingJoinRequest = JoinRequest(
|
||||
token = tokenBundle.token,
|
||||
@@ -851,6 +878,11 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_DEFAULT_CALL_TYPE = "default_call_type"
|
||||
const val EXTRA_CALL_ID = "call_id"
|
||||
const val DEFAULT_CALL_TYPE_P2P = "p2p"
|
||||
const val DEFAULT_CALL_TYPE_GROUP = "group"
|
||||
|
||||
private const val TAG = "InteractiveLiveActivity"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,887 +0,0 @@
|
||||
package com.demo.SellyCloudSDK.live
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.PixelFormat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.demo.SellyCloudSDK.R
|
||||
import com.demo.SellyCloudSDK.beauty.FaceUnityBeautyEngine
|
||||
import com.demo.SellyCloudSDK.databinding.ActivityMainBinding
|
||||
import com.sellycloud.sellycloudsdk.*
|
||||
import com.sellycloud.sellycloudsdk.PlayerConfig
|
||||
import com.sellycloud.sellycloudsdk.RtmpPlayer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.delay
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
import com.sellycloud.sellycloudsdk.Protocol
|
||||
import android.content.res.Configuration
|
||||
import org.webrtc.RendererCommon
|
||||
import kotlin.text.clear
|
||||
|
||||
class MainActivity : AppCompatActivity(),
|
||||
SurfaceHolder.Callback {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
// 单一 StreamingManager,按协议初始化
|
||||
private var streamingManager: StreamingManager? = null
|
||||
private val faceUnityBeautyEngine: FaceUnityBeautyEngine by lazy { FaceUnityBeautyEngine() }
|
||||
|
||||
// UI 状态助手
|
||||
private lateinit var uiState: UiStateManager
|
||||
|
||||
// 播放 Surface 管理器
|
||||
private lateinit var playSurfaceManager: PlaySurfaceManager
|
||||
|
||||
// WHEP 相关
|
||||
private var whepClient: WhepClient? = null
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private var isWhepPlaying = false
|
||||
private var whepSurfaceView: SurfaceViewRenderer? = null
|
||||
private var webrtcEglBase: org.webrtc.EglBase? = null
|
||||
|
||||
// 预览 Surface 就绪标志(RTMP 预览视图)
|
||||
private var isPushSurfaceReady = false
|
||||
|
||||
// 协议选择
|
||||
private var selectedProtocol: Protocol = Protocol.RTMP
|
||||
|
||||
// 播放类型枚举
|
||||
private enum class PlayType { NONE, RTMP, WHEP }
|
||||
private var currentPlayType = PlayType.NONE
|
||||
|
||||
// 播放器
|
||||
private var player: RtmpPlayer? = null
|
||||
private var playerConfig: PlayerConfig? = null
|
||||
private var isPlaySurfaceValid = false
|
||||
private var lastPlayUrl: String? = null
|
||||
private var shouldResumePlayback = false
|
||||
private var needRecreatePlayer = false
|
||||
|
||||
// 状态变量
|
||||
private var idelStatus = "待启动"
|
||||
|
||||
private val permissions =
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) arrayOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) else arrayOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
)
|
||||
|
||||
// 防止重复启动预览导致多次 GL / 美颜初始化
|
||||
private var hasStartedPushPreview = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
// 初始化 UI 与管理器
|
||||
uiState = UiStateManager(binding)
|
||||
playSurfaceManager = PlaySurfaceManager(binding.surfaceViewPlay)
|
||||
uiState.setRtmpButtonText(false)
|
||||
updateWhepButtonText()
|
||||
|
||||
// 屏幕常亮
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
// 初始化 StreamingManager 与监听
|
||||
streamingManager = StreamingManager(this).also { mgr ->
|
||||
mgr.setBeautyEngine(faceUnityBeautyEngine)
|
||||
mgr.setStreamingListener(object : StreamingListener {
|
||||
override fun onStateUpdate(state: StreamingState, message: String?, extras: Bundle?) {
|
||||
runOnUiThread {
|
||||
val text = message ?: when (state) {
|
||||
StreamingState.IDLE -> "待启动"
|
||||
StreamingState.CONNECTING -> "连接中..."
|
||||
StreamingState.STREAMING -> "推流中"
|
||||
StreamingState.RECONNECTING -> "重连中..."
|
||||
StreamingState.STOPPED -> "已停止"
|
||||
StreamingState.FAILED -> "推流错误"
|
||||
}
|
||||
val logMap = mapOf(
|
||||
"state" to state.name,
|
||||
"message" to message,
|
||||
"extras" to bundleToMap(extras)
|
||||
)
|
||||
Log.d("MainActivity111111", logMap.toString())
|
||||
uiState.setPushStatusText(text, idelStatus)
|
||||
uiState.setPushButtonsEnabled(state == StreamingState.STREAMING)
|
||||
setProtocolSelectionEnabled(state != StreamingState.STREAMING && state != StreamingState.CONNECTING && state != StreamingState.RECONNECTING)
|
||||
}
|
||||
}
|
||||
override fun onError(error: StreamingError) {
|
||||
runOnUiThread { Toast.makeText(this@MainActivity, error.message, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
})
|
||||
}
|
||||
// 默认 RTMP 预览标题
|
||||
val defaultId = if (selectedProtocol == Protocol.WHIP) R.id.rbProtocolWhip else R.id.rbProtocolRtmp
|
||||
if (binding.protocolGroup.checkedRadioButtonId != defaultId) {
|
||||
binding.protocolGroup.check(defaultId)
|
||||
}
|
||||
setPushPreviewHeader(selectedProtocol.name)
|
||||
// 绑定 UI 监听
|
||||
setupListeners()
|
||||
|
||||
// 权限
|
||||
checkAndRequestPermissions()
|
||||
}
|
||||
|
||||
// 接管屏幕方向变化,避免 Activity 重建导致两个预览销毁
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
// RTMP 推流预览(OpenGlView)保持像素格式与叠放层不变,仅请求重新布局
|
||||
try {
|
||||
binding.surfaceViewPlay.setZOrderMediaOverlay(false)
|
||||
binding.surfaceViewPlay.holder.setFormat(PixelFormat.OPAQUE)
|
||||
binding.surfaceViewPlay.requestLayout()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
// WHIP 推流预览(SurfaceViewRenderer)只调整缩放并请求布局
|
||||
try {
|
||||
binding.whipPreview.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
binding.whipPreview.setEnableHardwareScaler(true)
|
||||
binding.whipPreview.requestLayout()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
// 若当前是 WHEP 播放,动态渲染器同样更新缩放并请求布局
|
||||
try {
|
||||
whepSurfaceView?.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
whepSurfaceView?.setEnableHardwareScaler(true)
|
||||
whepSurfaceView?.requestLayout()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
// 播放 Surface 保持 RGBA_8888 与覆盖层,确保颜色/叠放正确
|
||||
try {
|
||||
binding.surfaceViewPlay.setZOrderMediaOverlay(true)
|
||||
ensurePlaySurfaceFormat()
|
||||
binding.surfaceViewPlay.requestLayout()
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// 恢复美颜/GL 管线
|
||||
// streamingManager?.onResume()
|
||||
// 恢复预览(RTMP/WHIP)
|
||||
if (isPushSurfaceReady) {
|
||||
try {
|
||||
streamingManager?.resumePreview()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
// 播放器复位
|
||||
if (needRecreatePlayer && isPlaySurfaceValid) {
|
||||
recreatePlayerAndMaybeResume()
|
||||
} else if (shouldResumePlayback && !lastPlayUrl.isNullOrEmpty()) {
|
||||
val holder = binding.surfaceViewPlay.holder
|
||||
if (holder.surface != null && holder.surface.isValid) {
|
||||
ensurePlaySurfaceFormat()
|
||||
player?.setSurface(holder.surface)
|
||||
player?.prepareAsync(lastPlayUrl!!)
|
||||
updateStatus(playStatus = "正在连接")
|
||||
updatePlayButtonStates(false)
|
||||
shouldResumePlayback = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// 暂停美颜/GL 管线
|
||||
// streamingManager?.onPause()
|
||||
// 暂停预览(RTMP/WHIP)
|
||||
if (selectedProtocol == Protocol.RTMP) {
|
||||
try { streamingManager?.pausePreview() } catch (_: Exception) {}
|
||||
} else if (selectedProtocol == Protocol.WHIP) {
|
||||
try { streamingManager?.stopWhipPreview() } catch (_: Exception) {}
|
||||
}
|
||||
// 播放侧资源处理
|
||||
shouldResumePlayback = player?.isPlaying() == true || player?.isPrepared() == true
|
||||
try { player?.setSurface(null) } catch (_: Exception) {}
|
||||
player?.release(); player = null
|
||||
needRecreatePlayer = true
|
||||
shouldResumePlayback = true
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
// 开始推流(根据协议)
|
||||
binding.btnStartPush.setOnClickListener {
|
||||
// 获取各个配置字段
|
||||
// val host = binding.etHost.text.toString().trim()
|
||||
val appName = binding.etAppName.text.toString().trim()
|
||||
val streamName = binding.etStreamName.text.toString().trim()
|
||||
// val streamKey = binding.etStreamKey.text.toString().trim()
|
||||
|
||||
// 验证必填字段
|
||||
// if (host.isEmpty()) {
|
||||
// Toast.makeText(this, "请输入Host地址", Toast.LENGTH_SHORT).show()
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
if (appName.isEmpty()) {
|
||||
Toast.makeText(this, "请输入App Name", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (streamName.isEmpty()) {
|
||||
Toast.makeText(this, "请输入Stream Name", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
streamingManager?.updateStreamConfig(
|
||||
host = "rtmp.sellycloud.push",
|
||||
appName = appName,
|
||||
streamName = streamName,
|
||||
streamKey = ""
|
||||
)
|
||||
streamingManager?.startStreaming()
|
||||
// 同步美颜
|
||||
streamingManager?.setBeautyEnabled(binding.switchBeauty.isChecked)
|
||||
}
|
||||
// 停止推流
|
||||
binding.btnStopPush.setOnClickListener { streamingManager?.stopStreaming() }
|
||||
// 协议选择监听
|
||||
binding.protocolGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
val newProtocol = if (checkedId == R.id.rbProtocolWhip) Protocol.WHIP else Protocol.RTMP
|
||||
if (newProtocol != selectedProtocol) {
|
||||
switchProtocol(newProtocol)
|
||||
}
|
||||
}
|
||||
// 切换摄像头
|
||||
binding.btnSwitchCamera.setOnClickListener { streamingManager?.switchCamera() }
|
||||
// 切换方向
|
||||
binding.btnSwitchOrientation.setOnClickListener { streamingManager?.switchOrientation() }
|
||||
// 镜像
|
||||
binding.cbPreviewHFlip.setOnCheckedChangeListener { _, h ->
|
||||
streamingManager?.setMirror(horizontal = h, vertical = binding.cbPreviewVFlip.isChecked)
|
||||
}
|
||||
binding.cbPreviewVFlip.setOnCheckedChangeListener { _, v ->
|
||||
streamingManager?.setMirror(horizontal = binding.cbPreviewHFlip.isChecked, vertical = v)
|
||||
}
|
||||
// 美颜
|
||||
binding.switchBeauty.setOnCheckedChangeListener { _, on ->
|
||||
streamingManager?.setBeautyEnabled(on)
|
||||
Toast.makeText(this, "美颜功能${if (on) "开启" else "关闭"}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
binding.switchBeauty.setOnLongClickListener { Toast.makeText(this, "高级美颜面板暂未开放", Toast.LENGTH_SHORT).show(); true }
|
||||
// 分辨率
|
||||
binding.resolutionGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
val (w, h) = when (checkedId) {
|
||||
R.id.res360p -> 360 to 640
|
||||
R.id.res540p -> 540 to 960
|
||||
R.id.res720p -> 720 to 1280
|
||||
R.id.res1080p -> 1080 to 1920
|
||||
else -> 720 to 1280
|
||||
}
|
||||
streamingManager?.changeResolution(w, h)
|
||||
}
|
||||
// 选择图片作为视频源
|
||||
val pickImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||
if (uri == null) { Toast.makeText(this, "未选择图片", Toast.LENGTH_SHORT).show(); return@registerForActivityResult }
|
||||
try {
|
||||
val bmp = decodeBitmapFromUri(uri)
|
||||
if (bmp != null) {
|
||||
val ok = streamingManager?.setBitmapAsVideoSource(bmp)
|
||||
Toast.makeText(this, if (ok == true) "已切换为图片源" else "暂不支持该分辨率/失败", Toast.LENGTH_SHORT).show()
|
||||
} else Toast.makeText(this, "图片解码失败", Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) { Toast.makeText(this, "设置图片源失败: ${e.message}", Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
binding.btnChooseImageSource.setOnClickListener { pickImage.launch("image/*") }
|
||||
// 恢复摄像头视频源
|
||||
binding.btnRestoreCamera.setOnClickListener { streamingManager?.restoreCameraVideoSource() }
|
||||
|
||||
// RTMP 播放:单按钮切换 开始/停止
|
||||
binding.btnPlay.setOnClickListener {
|
||||
if (currentPlayType == PlayType.RTMP) {
|
||||
stopCurrentPlayback()
|
||||
return@setOnClickListener
|
||||
}
|
||||
val playAppName = binding.etPlayAppName.text.toString().trim()
|
||||
val playStreamName = binding.etPlayStreamName.text.toString().trim()
|
||||
|
||||
if (playAppName.isEmpty()) { Toast.makeText(this, "请输入Play App Name", Toast.LENGTH_SHORT).show(); return@setOnClickListener }
|
||||
if (playStreamName.isEmpty()) { Toast.makeText(this, "请输入Play Stream Name", Toast.LENGTH_SHORT).show(); return@setOnClickListener }
|
||||
|
||||
val url = buildPlayUrl("RTMP", playAppName, playStreamName)
|
||||
Toast.makeText(this, "播放地址: $url", Toast.LENGTH_SHORT).show()
|
||||
|
||||
stopCurrentPlayback() // 停止其他播放(如WHEP)
|
||||
if (isPlaySurfaceValid) {
|
||||
ensurePlaySurfaceFormat()
|
||||
lastPlayUrl = url
|
||||
currentPlayType = PlayType.RTMP
|
||||
player?.setSurface(binding.surfaceViewPlay.holder.surface)
|
||||
player?.prepareAsync(url)
|
||||
updatePlayButtonStates(false)
|
||||
uiState.setRtmpButtonText(true)
|
||||
updateStatus(playStatus = "正在连接(RTMP)")
|
||||
} else Toast.makeText(this, "播放 Surface 未准备好", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// 已移除独立的停止播放按钮
|
||||
// WHEP 拉流:单按钮切换 开始/停止
|
||||
binding.btnWhepPlay.setOnClickListener {
|
||||
if (isWhepPlaying) {
|
||||
stopWhepStreaming()
|
||||
} else {
|
||||
val playAppName = binding.etPlayAppName.text.toString().trim()
|
||||
val playStreamName = binding.etPlayStreamName.text.toString().trim()
|
||||
if (playAppName.isEmpty()) { Toast.makeText(this, "请输入Play App Name", Toast.LENGTH_SHORT).show(); return@setOnClickListener }
|
||||
if (playStreamName.isEmpty()) { Toast.makeText(this, "请输入Play Stream Name", Toast.LENGTH_SHORT).show(); return@setOnClickListener }
|
||||
val url = buildPlayUrl("WHEP", playAppName, playStreamName)
|
||||
Toast.makeText(this, "播放地址: $url", Toast.LENGTH_SHORT).show()
|
||||
startWhepStreaming(url)
|
||||
}
|
||||
}
|
||||
|
||||
// 截图:推流预览
|
||||
binding.btnCapturePush.setOnClickListener {
|
||||
val targetView: View? = if (selectedProtocol == Protocol.WHIP) binding.whipPreview else binding.surfaceViewPush
|
||||
captureSurfaceViewAndSave(targetView, prefix = "push")
|
||||
}
|
||||
// 截图:播放
|
||||
binding.btnCapturePlay.setOnClickListener {
|
||||
val targetView: View? = if (isWhepPlaying) whepSurfaceView else binding.surfaceViewPlay
|
||||
captureSurfaceViewAndSave(targetView, prefix = "play")
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 协议切换 ---------- */
|
||||
private fun switchProtocol(newProtocol: Protocol) {
|
||||
if (newProtocol == Protocol.RTMP) {
|
||||
binding.surfaceViewPush.visibility = View.VISIBLE
|
||||
binding.whipPreview.visibility = View.GONE
|
||||
setPushPreviewHeader("RTMP")
|
||||
} else {
|
||||
binding.surfaceViewPush.visibility = View.GONE
|
||||
binding.whipPreview.visibility = View.VISIBLE
|
||||
setPushPreviewHeader("WHIP")
|
||||
}
|
||||
|
||||
// 根据协议选择对应视图
|
||||
val targetView = if (newProtocol == Protocol.RTMP) {
|
||||
binding.surfaceViewPush
|
||||
} else {
|
||||
binding.whipPreview
|
||||
}
|
||||
selectedProtocol = newProtocol
|
||||
streamingManager?.switchProtocol(newProtocol, targetView)
|
||||
|
||||
}
|
||||
|
||||
private fun setProtocolSelectionEnabled(enabled: Boolean) {
|
||||
binding.protocolGroup.isEnabled = enabled
|
||||
binding.rbProtocolRtmp.isEnabled = enabled
|
||||
binding.rbProtocolWhip.isEnabled = enabled
|
||||
}
|
||||
|
||||
/* ---------- SurfaceHolder.Callback ---------- */
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
when (holder.surface) {
|
||||
binding.surfaceViewPlay.holder.surface -> {
|
||||
isPlaySurfaceValid = true
|
||||
ensurePlaySurfaceFormat()
|
||||
if (needRecreatePlayer) {
|
||||
recreatePlayerAndMaybeResume()
|
||||
} else {
|
||||
player?.setSurface(holder.surface)
|
||||
if (shouldResumePlayback && !lastPlayUrl.isNullOrEmpty()) {
|
||||
player?.prepareAsync(lastPlayUrl!!)
|
||||
updateStatus(playStatus = "正在连接")
|
||||
updatePlayButtonStates(false)
|
||||
shouldResumePlayback = false
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.surfaceViewPush.holder.surface -> {
|
||||
isPushSurfaceReady = true
|
||||
//打印日志
|
||||
Log.d("MainActivity", "Push surface created")
|
||||
Log.d("MainActivity" , hasStartedPushPreview.toString())
|
||||
// 仅首次或重建后启动预览,避免重复 startPreview + 触发多次美颜加载
|
||||
if (!hasStartedPushPreview) {
|
||||
try {
|
||||
streamingManager?.startPreview(); hasStartedPushPreview = true
|
||||
streamingManager?.setBeautyEnabled(binding.switchBeauty.isChecked)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
if (holder.surface == binding.surfaceViewPlay.holder.surface) ensurePlaySurfaceFormat()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
when (holder.surface) {
|
||||
binding.surfaceViewPlay.holder.surface -> {
|
||||
isPlaySurfaceValid = false
|
||||
player?.setSurface(null)
|
||||
}
|
||||
binding.surfaceViewPush.holder.surface -> {
|
||||
Log.d("MainActivity", "Push surface destroyed")
|
||||
isPushSurfaceReady = false
|
||||
hasStartedPushPreview = false // 下次重建允许重新 startPreview
|
||||
// 释放摄像头/预览由流程统一处理
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 权限检测与申请 */
|
||||
private fun checkAndRequestPermissions() {
|
||||
if (permissions.all { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED }) {
|
||||
setupAll()
|
||||
} else {
|
||||
permissionLauncher.launch(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
private val permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { results ->
|
||||
if (results.values.all { it }) setupAll() else Toast.makeText(this, "需要相机和录音权限才能使用此功能", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
/** 初始化(推流 + 播放) */
|
||||
private fun setupAll() {
|
||||
// 默认显示 RTMP 预览视图
|
||||
binding.surfaceViewPush.visibility = if (selectedProtocol == Protocol.RTMP) View.VISIBLE else View.GONE
|
||||
binding.whipPreview.visibility = if (selectedProtocol == Protocol.WHIP) View.VISIBLE else View.GONE
|
||||
|
||||
// RTMP 预览器配置
|
||||
binding.surfaceViewPlay.setZOrderMediaOverlay(false)
|
||||
binding.surfaceViewPlay.holder.setFormat(PixelFormat.OPAQUE)
|
||||
// 由 Surface 回调驱动 RTMP 预览生命周期
|
||||
binding.surfaceViewPush.holder.addCallback(this)
|
||||
|
||||
binding.whipPreview.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL )
|
||||
binding.whipPreview.setEnableHardwareScaler(true)
|
||||
|
||||
val (w, h) = currentResolution()
|
||||
//配置参数
|
||||
streamingManager?.updateStreamConfig(
|
||||
protocol = selectedProtocol,
|
||||
width = 1080,
|
||||
height = 1920,
|
||||
fps = 40,
|
||||
videoBitrate = 2_500_000,
|
||||
audioBitrate = 128_000,
|
||||
iFrameInterval = 1,
|
||||
maxRetryCount = 5,
|
||||
retryDelayMs = 3000,
|
||||
facing = "front"
|
||||
)
|
||||
|
||||
// 初始化 manager 与预览
|
||||
try {
|
||||
if (selectedProtocol == Protocol.RTMP) {
|
||||
streamingManager?.initialize(binding.surfaceViewPush)
|
||||
} else {
|
||||
streamingManager?.initialize(binding.whipPreview)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
// 播放器与回调维持原逻辑
|
||||
playerConfig = PlayerConfig.forRtmpLive(enableKiwi = true, rsname = "123")
|
||||
player = RtmpPlayer(context = this, playerConfig = playerConfig!!)
|
||||
attachRtmpPlayerStateListener()
|
||||
binding.surfaceViewPlay.setZOrderMediaOverlay(false)
|
||||
binding.surfaceViewPlay.holder.setFormat(PixelFormat.OPAQUE)
|
||||
binding.surfaceViewPlay.holder.addCallback(this)
|
||||
if (binding.surfaceViewPlay.holder.surface.isValid) {
|
||||
surfaceCreated(binding.surfaceViewPlay.holder)
|
||||
}
|
||||
}
|
||||
|
||||
/** 将Uri解码成合适大小的Bitmap,避免OOM */
|
||||
private fun decodeBitmapFromUri(uri: Uri): Bitmap? {
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
contentResolver.openInputStream(uri)?.use { input -> BitmapFactory.decodeStream(input, null, options) }
|
||||
val reqMax = 1280
|
||||
var inSample = 1
|
||||
val w = options.outWidth; val h = options.outHeight
|
||||
if (w > reqMax || h > reqMax) {
|
||||
val halfW = w / 2; val halfH = h / 2
|
||||
while ((halfW / inSample) >= reqMax || (halfH / inSample) >= reqMax) { inSample *= 2 }
|
||||
}
|
||||
val decodeOpts = BitmapFactory.Options().apply { inSampleSize = inSample }
|
||||
contentResolver.openInputStream(uri)?.use { input -> return BitmapFactory.decodeStream(input, null, decodeOpts) }
|
||||
return null
|
||||
}
|
||||
|
||||
/** 更新状态文本 */
|
||||
private fun updateStatus(pushStatus: String? = null, playStatus: String? = null) {
|
||||
runOnUiThread {
|
||||
val currentPushStatus = binding.tvStatus.text.split("|")[0].split(":").getOrNull(1)?.trim() ?: "待启动"
|
||||
val newPushStatus = pushStatus ?: currentPushStatus
|
||||
if (playStatus != null) this.idelStatus = playStatus
|
||||
uiState.setPushStatusText(newPushStatus, this.idelStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePlayButtonStates(enabled: Boolean) { runOnUiThread { uiState.setPlayButtonEnabled(enabled) } }
|
||||
private fun setPushPreviewHeader(mode: String) { try { uiState.setPushPreviewHeader(mode) } catch (_: Exception) {} }
|
||||
private fun updateWhepButtonText() { uiState.setWhepButtonText(isWhepPlaying) }
|
||||
|
||||
private fun stopCurrentPlayback() {
|
||||
when (currentPlayType) {
|
||||
PlayType.RTMP -> {
|
||||
try { player?.setSurface(null) } catch (_: Exception) {}
|
||||
try { player?.stop() } catch (_: Exception) {}
|
||||
try { player?.release() } catch (_: Exception) {}
|
||||
try { player?.destroy() } catch (_: Exception) {}
|
||||
|
||||
val cfg = playerConfig ?: PlayerConfig.forRtmpLive(enableKiwi = true, rsname = "123")
|
||||
player = RtmpPlayer(context = this, playerConfig = cfg)
|
||||
attachRtmpPlayerStateListener()
|
||||
|
||||
lastPlayUrl = null
|
||||
shouldResumePlayback = false
|
||||
currentPlayType = PlayType.NONE
|
||||
updatePlayButtonStates(true)
|
||||
uiState.setRtmpButtonText(false)
|
||||
updateStatus(playStatus = "已停止播放")
|
||||
|
||||
forceRecreatePlaySurface()
|
||||
}
|
||||
PlayType.WHEP -> stopWhepStreaming()
|
||||
PlayType.NONE -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// 强制销毁并重建播放 Surface(通过可见性切换触发 surfaceDestroyed/surfaceCreated)
|
||||
private fun forceRecreatePlaySurface() {
|
||||
try {
|
||||
binding.surfaceViewPlay.visibility = View.GONE
|
||||
binding.surfaceViewPlay.post {
|
||||
ensurePlaySurfaceFormat()
|
||||
binding.surfaceViewPlay.visibility = View.VISIBLE
|
||||
binding.surfaceViewPlay.requestLayout()
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
/* ---------- WHEP 功能(保留) ---------- */
|
||||
private fun startWhepStreaming(url: String) {
|
||||
stopCurrentPlayback()
|
||||
val whepUrl = url
|
||||
try {
|
||||
// 初始化 WHEP SurfaceViewRenderer
|
||||
if (whepSurfaceView == null) {
|
||||
whepSurfaceView = SurfaceViewRenderer(this)
|
||||
runOnUiThread {
|
||||
whepSurfaceView?.let { surfaceView ->
|
||||
try {
|
||||
if (webrtcEglBase == null) webrtcEglBase = org.webrtc.EglBase.create()
|
||||
surfaceView.init(webrtcEglBase!!.eglBaseContext, null)
|
||||
surfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
|
||||
surfaceView.setEnableHardwareScaler(true)
|
||||
val playContainer = binding.surfaceViewPlay.parent as android.view.ViewGroup
|
||||
val layoutParams = android.view.ViewGroup.LayoutParams(
|
||||
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
surfaceView.layoutParams = layoutParams
|
||||
surfaceView.setZOrderOnTop(false)
|
||||
surfaceView.setZOrderMediaOverlay(true)
|
||||
playContainer.addView(surfaceView)
|
||||
binding.surfaceViewPlay.visibility = View.GONE
|
||||
surfaceView.visibility = View.VISIBLE
|
||||
playContainer.requestLayout(); surfaceView.requestLayout()
|
||||
} catch (e: Exception) { Log.e("MainActivity", "Error initializing WHEP view", e); throw e }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 启动播放
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
delay(300)
|
||||
whepClient = WhepClient(this@MainActivity, coroutineScope, whepSurfaceView!!, webrtcEglBase!!.eglBaseContext)
|
||||
attachWhepPlayerStateListener()
|
||||
whepClient?.play(whepUrl)
|
||||
runOnUiThread {
|
||||
isWhepPlaying = true
|
||||
currentPlayType = PlayType.WHEP
|
||||
updateWhepButtonText()
|
||||
updatePlayButtonStates(false)
|
||||
Toast.makeText(this@MainActivity, "WHEP拉流已启动", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
isWhepPlaying = false; whepClient = null; currentPlayType = PlayType.NONE
|
||||
updateWhepButtonText(); updatePlayButtonStates(true)
|
||||
updateStatus(playStatus = "WHEP播放失败: ${e.message}")
|
||||
Toast.makeText(this@MainActivity, "WHEP拉流启动失败: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
whepSurfaceView?.let { surfaceView ->
|
||||
try { val parent = surfaceView.parent as? android.view.ViewGroup; parent?.removeView(surfaceView) } catch (_: Exception) {}
|
||||
}
|
||||
whepSurfaceView = null
|
||||
binding.surfaceViewPlay.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) { Toast.makeText(this, "WHEP拉流初始化失败: ${e.message}", Toast.LENGTH_LONG).show(); updateStatus(playStatus = "WHEP初始化失败") }
|
||||
}
|
||||
|
||||
private fun stopWhepStreaming() {
|
||||
try { whepClient?.stop(); whepClient = null; runOnUiThread {
|
||||
whepSurfaceView?.let { surfaceView ->
|
||||
try { surfaceView.release() } catch (_: Exception) {}
|
||||
val parent = surfaceView.parent as? android.view.ViewGroup; parent?.removeView(surfaceView)
|
||||
}
|
||||
whepSurfaceView = null
|
||||
binding.surfaceViewPlay.visibility = View.VISIBLE
|
||||
ensurePlaySurfaceFormat()
|
||||
} } catch (_: Exception) {}
|
||||
try { webrtcEglBase?.release() } catch (_: Exception) {}
|
||||
webrtcEglBase = null
|
||||
isWhepPlaying = false; currentPlayType = PlayType.NONE
|
||||
updateWhepButtonText(); updatePlayButtonStates(true)
|
||||
updateStatus(playStatus = "WHEP播放已停止")
|
||||
Toast.makeText(this, "WHEP拉流已停止", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// 停止 WHEP
|
||||
if (isWhepPlaying) stopWhepStreaming()
|
||||
try { webrtcEglBase?.release(); webrtcEglBase = null } catch (_: Exception) {}
|
||||
// 释放 StreamingManager
|
||||
streamingManager?.release(); streamingManager = null
|
||||
// 完整销毁播放器(包含协程作用域和 native profile)
|
||||
try { player?.destroy() } catch (_: Exception) { try { player?.release() } catch (_: Exception) {} }
|
||||
player = null
|
||||
|
||||
coroutineScope.cancel()
|
||||
try { binding.surfaceViewPlay.holder.removeCallback(this) } catch (_: Exception) {}
|
||||
try { binding.surfaceViewPush.holder.removeCallback(this) } catch (_: Exception) {}
|
||||
try { coroutineScope.cancel() } catch (_: Exception) {}
|
||||
}
|
||||
|
||||
private fun recreatePlayerAndMaybeResume() {
|
||||
val cfg = playerConfig ?: PlayerConfig.forRtmpLive(enableKiwi = true, rsname = "123")
|
||||
player = RtmpPlayer(context = this, playerConfig = cfg)
|
||||
attachRtmpPlayerStateListener()
|
||||
ensurePlaySurfaceFormat()
|
||||
val holder = binding.surfaceViewPlay.holder
|
||||
if (holder.surface != null && holder.surface.isValid) player?.setSurface(holder.surface)
|
||||
if (shouldResumePlayback && !lastPlayUrl.isNullOrEmpty()) {
|
||||
player?.prepareAsync(lastPlayUrl!!)
|
||||
updatePlayButtonStates(false)
|
||||
uiState.setRtmpButtonText(true)
|
||||
shouldResumePlayback = false
|
||||
}
|
||||
needRecreatePlayer = false
|
||||
}
|
||||
|
||||
private fun attachRtmpPlayerStateListener() {
|
||||
player?.setSCPlayerStateListener { state, detail ->
|
||||
Log.d("MainActivity", "Player State: $state, Detail: $detail")
|
||||
when (state) {
|
||||
SCPlayerState.SCPlayerStateConnecting -> runOnUiThread {
|
||||
val reconnect = detail?.contains("reconnecting") == true
|
||||
updateStatus(playStatus = if (reconnect) "正在重连(RTMP)" else "正在连接(RTMP)")
|
||||
updatePlayButtonStates(false)
|
||||
uiState.setRtmpButtonText(true)
|
||||
}
|
||||
SCPlayerState.SCPlayerStatePlaying -> runOnUiThread { updateStatus(playStatus = "播放中(RTMP)"); updatePlayButtonStates(false); uiState.setRtmpButtonText(true) }
|
||||
SCPlayerState.SCPlayerStatePaused -> runOnUiThread { updateStatus(playStatus = "暂停播放(RTMP)") }
|
||||
SCPlayerState.SCPlayerStateStoppedOrEnded -> runOnUiThread {
|
||||
val text = if (detail == "completed") "播放完成(RTMP)" else "已结束播放(RTMP)"
|
||||
updateStatus(playStatus = text); updatePlayButtonStates(true); uiState.setRtmpButtonText(false)
|
||||
try { player?.setSurface(null) } catch (_: Exception) {}
|
||||
playSurfaceManager.clear()
|
||||
}
|
||||
SCPlayerState.SCPlayerStateFailed -> runOnUiThread {
|
||||
updateStatus(playStatus = "播放错误(RTMP)"); updatePlayButtonStates(true); uiState.setRtmpButtonText(false)
|
||||
try { player?.setSurface(null) } catch (_: Exception) {}
|
||||
playSurfaceManager.clear()
|
||||
}
|
||||
SCPlayerState.SCPlayerStateIdle -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachWhepPlayerStateListener() {
|
||||
whepClient?.setSCPlayerStateListener { state, detail ->
|
||||
Log.d("MainActivity", "WHEP Player State: $state, Detail: $detail")
|
||||
when (state) {
|
||||
SCPlayerState.SCPlayerStateConnecting -> runOnUiThread {
|
||||
val statusText = if (detail == "ICE connected") "已连接(WHEP)" else "正在连接(WHEP)"
|
||||
updateStatus(playStatus = statusText); updatePlayButtonStates(false)
|
||||
}
|
||||
SCPlayerState.SCPlayerStatePlaying -> runOnUiThread { updateStatus(playStatus = "播放中(WHEP)"); updatePlayButtonStates(false) }
|
||||
SCPlayerState.SCPlayerStateStoppedOrEnded -> runOnUiThread {
|
||||
isWhepPlaying = false
|
||||
updateStatus(playStatus = "WHEP播放已停止"); updatePlayButtonStates(true)
|
||||
updateWhepButtonText()
|
||||
}
|
||||
SCPlayerState.SCPlayerStateFailed -> runOnUiThread {
|
||||
isWhepPlaying = false
|
||||
updateStatus(playStatus = "WHEP失败: ${detail ?: "未知错误"}"); updatePlayButtonStates(true)
|
||||
updateWhepButtonText()
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算当前选中分辨率,统一竖屏(宽<高) */
|
||||
private fun currentResolution(): Pair<Int, Int> {
|
||||
var (w, h) = when (binding.resolutionGroup.checkedRadioButtonId) {
|
||||
R.id.res360p -> 360 to 640
|
||||
R.id.res540p -> 540 to 960
|
||||
R.id.res720p -> 720 to 1280
|
||||
else -> 720 to 1280
|
||||
}
|
||||
if (w > h) { val t = w; w = h; h = t }
|
||||
return w to h
|
||||
}
|
||||
|
||||
/** 根据不同协议组装播放 URL */
|
||||
private fun buildPlayUrl(protocolType: String, appName: String, streamName: String): String {
|
||||
return when (protocolType) {
|
||||
"RTMP" -> {
|
||||
// RTMP 播放格式: rtmp://rtmp.sellycloud.pull/appName/streamName
|
||||
"rtmp://rtmp.sellycloud.pull/$appName/$streamName"
|
||||
}
|
||||
"WHEP" -> {
|
||||
// WHEP 播放格式 (WHIP推流对应WHEP拉流): https://rtmp.sellycloud.pull/whep/appName/streamName
|
||||
"http://rtmp.sellycloud.pull/$appName/$streamName"
|
||||
}
|
||||
else -> {
|
||||
// 默认使用 RTMP
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 确保播放 Surface 的像素格式与叠放层设置正确(防止再次播放偏蓝) */
|
||||
private fun ensurePlaySurfaceFormat() {
|
||||
playSurfaceManager.ensureOpaqueFormat()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** 使用 PixelCopy 截取 Surface 内容并保存到相册(Android 8.0+)。更低版本给出提示。 */
|
||||
private fun captureSurfaceViewAndSave(view: View?, prefix: String) {
|
||||
if (view == null) { Toast.makeText(this, "当前没有可用的视图进行截图", Toast.LENGTH_SHORT).show(); return }
|
||||
if (view.width <= 0 || view.height <= 0) { Toast.makeText(this, "视图尚未布局完成,稍后再试", Toast.LENGTH_SHORT).show(); return }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
Toast.makeText(this, "当前系统版本不支持该截图方式(需Android 8.0+)", Toast.LENGTH_LONG).show(); return
|
||||
}
|
||||
// 仅支持 SurfaceView/其子类
|
||||
if (view !is android.view.SurfaceView) {
|
||||
Toast.makeText(this, "当前视图不支持截图", Toast.LENGTH_SHORT).show(); return
|
||||
}
|
||||
val bmp = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
|
||||
try {
|
||||
val handler = android.os.Handler(mainLooper)
|
||||
android.view.PixelCopy.request(view, bmp, { result ->
|
||||
if (result == android.view.PixelCopy.SUCCESS) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
val ok = saveBitmapToGallery(bmp, prefix)
|
||||
launch(Dispatchers.Main) {
|
||||
Toast.makeText(this@MainActivity, if (ok) "截图已保存到相册" else "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "截图失败,错误码: $result", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}, handler)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "截图异常: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存位图到系统相册(按API等级分别处理) */
|
||||
private fun saveBitmapToGallery(bitmap: Bitmap, prefix: String): Boolean {
|
||||
val filename = "${prefix}_${java.text.SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.getDefault()).format(java.util.Date())}.png"
|
||||
return try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val values = android.content.ContentValues().apply {
|
||||
put(android.provider.MediaStore.Images.Media.DISPLAY_NAME, filename)
|
||||
put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/png")
|
||||
put(android.provider.MediaStore.Images.Media.RELATIVE_PATH, "Pictures/")
|
||||
put(android.provider.MediaStore.Images.Media.IS_PENDING, 1)
|
||||
}
|
||||
val resolver = contentResolver
|
||||
val uri = resolver.insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
if (uri != null) {
|
||||
resolver.openOutputStream(uri)?.use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
values.clear()
|
||||
values.put(android.provider.MediaStore.Images.Media.IS_PENDING, 0)
|
||||
resolver.update(uri, values, null, null)
|
||||
true
|
||||
} else false
|
||||
} else {
|
||||
// API 29 以下,保存到公共图片目录(需要WRITE_EXTERNAL_STORAGE权限,已在Manifest按maxSdk申明)
|
||||
val picturesDir = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_PICTURES)
|
||||
val targetDir = java.io.File(picturesDir, "RTMPDemo").apply { if (!exists()) mkdirs() }
|
||||
val file = java.io.File(targetDir, filename)
|
||||
java.io.FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) }
|
||||
// 通知相册扫描
|
||||
val values = android.content.ContentValues().apply {
|
||||
put(android.provider.MediaStore.Images.Media.DATA, file.absolutePath)
|
||||
put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/png")
|
||||
put(android.provider.MediaStore.Images.Media.DISPLAY_NAME, filename)
|
||||
}
|
||||
contentResolver.insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "saveBitmapToGallery error", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun bundleToMap(bundle: Bundle?): Map<String, Any?> {
|
||||
if (bundle == null) return emptyMap()
|
||||
val map = mutableMapOf<String, Any?>()
|
||||
for (key in bundle.keySet()) {
|
||||
val value = bundle.get(key)
|
||||
map[key] = when (value) {
|
||||
is Bundle -> bundleToMap(value)
|
||||
is IntArray -> value.toList()
|
||||
is LongArray -> value.toList()
|
||||
is FloatArray -> value.toList()
|
||||
is DoubleArray -> value.toList()
|
||||
is BooleanArray -> value.toList()
|
||||
is ByteArray -> value.joinToString(prefix = "[", postfix = "]")
|
||||
is Array<*> -> value.toList()
|
||||
else -> value
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package com.demo.SellyCloudSDK.live
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.demo.SellyCloudSDK.R
|
||||
import com.sellycloud.sellycloudsdk.MultiRtmpPlayer
|
||||
import com.sellycloud.sellycloudsdk.PlayerConfig
|
||||
|
||||
class MultiPlayActivity : AppCompatActivity(), MultiRtmpPlayer.MultiRtmpPlayerListener {
|
||||
|
||||
private lateinit var etNewUrl: EditText
|
||||
private lateinit var btnAddStream: Button
|
||||
private lateinit var btnStartAll: Button
|
||||
private lateinit var btnStopAll: Button
|
||||
private lateinit var streamsContainer: LinearLayout
|
||||
|
||||
private lateinit var multiPlayer: MultiRtmpPlayer
|
||||
private var streamCounter = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_multi_play)
|
||||
|
||||
etNewUrl = findViewById(R.id.etNewUrl)
|
||||
btnAddStream = findViewById(R.id.btnAddStream)
|
||||
btnStartAll = findViewById(R.id.btnStartAll)
|
||||
btnStopAll = findViewById(R.id.btnStopAll)
|
||||
streamsContainer = findViewById(R.id.streamsContainer)
|
||||
|
||||
multiPlayer = MultiRtmpPlayer(this, this, PlayerConfig.forRtmpLive())
|
||||
|
||||
btnAddStream.setOnClickListener {
|
||||
val urlInput = etNewUrl.text.toString().trim()
|
||||
val id = "stream_${streamCounter++}"
|
||||
|
||||
val config = if (urlInput.isEmpty()) {
|
||||
// 未输入 URL 时,示例启用 Kiwi 使用默认 rs 标识,可按需替换
|
||||
PlayerConfig.forRtmpLive(enableKiwi = true, rsname = "123")
|
||||
} else {
|
||||
PlayerConfig.forRtmpLive()
|
||||
}
|
||||
|
||||
val ok = multiPlayer.addStream(id, urlInput.ifEmpty { "rtmp://placeholder/kiwi" }, config)
|
||||
if (!ok) {
|
||||
Toast.makeText(this, "添加失败:ID重复", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
addStreamItemView(id)
|
||||
}
|
||||
|
||||
btnStartAll.setOnClickListener {
|
||||
multiPlayer.currentStreams().forEach { id ->
|
||||
if (multiPlayer.isPrepared(id)) multiPlayer.start(id) else multiPlayer.prepareAsync(id)
|
||||
}
|
||||
}
|
||||
btnStopAll.setOnClickListener {
|
||||
multiPlayer.currentStreams().forEach { id -> multiPlayer.stop(id) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun addStreamItemView(streamId: String) {
|
||||
val item = LayoutInflater.from(this).inflate(R.layout.item_stream_player, streamsContainer, false)
|
||||
val tvTitle = item.findViewById<TextView>(R.id.tvTitle)
|
||||
val tvStatus = item.findViewById<TextView>(R.id.tvStatus)
|
||||
val surfaceView = item.findViewById<SurfaceView>(R.id.surfaceView)
|
||||
val btnPrepare = item.findViewById<Button>(R.id.btnPrepare)
|
||||
val btnStart = item.findViewById<Button>(R.id.btnStart)
|
||||
val btnStop = item.findViewById<Button>(R.id.btnStop)
|
||||
val btnRemove = item.findViewById<Button>(R.id.btnRemove)
|
||||
|
||||
tvTitle.text = "流: $streamId"
|
||||
tvStatus.text = "状态: 已添加,待准备"
|
||||
|
||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
multiPlayer.setSurface(streamId, holder.surface)
|
||||
}
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
multiPlayer.setSurface(streamId, null)
|
||||
}
|
||||
})
|
||||
|
||||
btnPrepare.setOnClickListener {
|
||||
tvStatus.text = "状态: 准备中"
|
||||
multiPlayer.prepareAsync(streamId)
|
||||
}
|
||||
btnStart.setOnClickListener {
|
||||
if (multiPlayer.isPrepared(streamId)) {
|
||||
multiPlayer.start(streamId)
|
||||
} else {
|
||||
Toast.makeText(this, "请先准备该流", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
btnStop.setOnClickListener { multiPlayer.stop(streamId) }
|
||||
btnRemove.setOnClickListener {
|
||||
multiPlayer.release(streamId)
|
||||
streamsContainer.removeView(item)
|
||||
}
|
||||
|
||||
streamsContainer.addView(item)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
multiPlayer.releaseAll()
|
||||
}
|
||||
|
||||
// MultiRtmpPlayerListener 实现
|
||||
override fun onPlayerPrepared(streamId: String) {
|
||||
updateStatus(streamId, "准备完成")
|
||||
}
|
||||
|
||||
override fun onPlayerStarted(streamId: String) {
|
||||
updateStatus(streamId, "播放中")
|
||||
}
|
||||
|
||||
override fun onPlayerError(streamId: String, error: String) {
|
||||
updateStatus(streamId, "错误: $error")
|
||||
}
|
||||
|
||||
override fun onPlayerCompleted(streamId: String) {
|
||||
updateStatus(streamId, "播放完成")
|
||||
}
|
||||
|
||||
override fun onPlayerBuffering(streamId: String, percent: Int) {
|
||||
updateStatus(streamId, "缓冲中...$percent%")
|
||||
}
|
||||
|
||||
override fun onPlayerInfo(streamId: String, what: Int, extra: Int) {
|
||||
// 可按需处理更多 info
|
||||
}
|
||||
|
||||
private fun updateStatus(streamId: String, status: String) {
|
||||
runOnUiThread {
|
||||
for (i in 0 until streamsContainer.childCount) {
|
||||
val item = streamsContainer.getChildAt(i)
|
||||
val title = item.findViewById<TextView>(R.id.tvTitle)
|
||||
if (title.text.endsWith(streamId)) {
|
||||
val tvStatus = item.findViewById<TextView>(R.id.tvStatus)
|
||||
tvStatus.text = "状态: $status"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.demo.SellyCloudSDK.live
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.PixelFormat
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
|
||||
/**
|
||||
* Minimal manager for the playback SurfaceView to keep MainActivity lean.
|
||||
* Encapsulates pixel format setup and clearing the surface to black.
|
||||
*/
|
||||
class PlaySurfaceManager(private val surfaceView: SurfaceView) {
|
||||
|
||||
// 用于视频播放,使用 OPAQUE 格式避免颜色问题
|
||||
fun ensureOpaqueFormat() {
|
||||
surfaceView.setZOrderMediaOverlay(false)
|
||||
surfaceView.setZOrderOnTop(false)
|
||||
surfaceView.holder.setFormat(PixelFormat.OPAQUE)
|
||||
}
|
||||
fun clear() {
|
||||
val holder: SurfaceHolder = surfaceView.holder
|
||||
try {
|
||||
val canvas = holder.lockCanvas()
|
||||
if (canvas != null) {
|
||||
canvas.drawColor(Color.BLACK)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.demo.SellyCloudSDK.live
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.SurfaceHolder
|
||||
import com.demo.SellyCloudSDK.databinding.ActivityMainBinding
|
||||
|
||||
/**
|
||||
* Thin UI state helper to centralize status text and button states.
|
||||
* MainActivity delegates to this manager to reduce duplication.
|
||||
* No business logic is changed.
|
||||
*/
|
||||
class UiStateManager(private val binding: ActivityMainBinding) {
|
||||
|
||||
fun setPushStatusText(text: String, currentPlayStatus: String) {
|
||||
binding.tvStatus.text = "推流状态: $text | 播放状态: $currentPlayStatus"
|
||||
}
|
||||
|
||||
fun setPlayStatusText(currentPushStatus: String, playStatus: String) {
|
||||
binding.tvStatus.text = "推流状态: $currentPushStatus | 播放状态: $playStatus"
|
||||
}
|
||||
|
||||
fun setPushButtonsEnabled(isPushing: Boolean) {
|
||||
binding.btnStartPush.isEnabled = !isPushing
|
||||
binding.btnStopPush.isEnabled = isPushing
|
||||
}
|
||||
|
||||
// Keep the RTMP play button enabled so it can serve as a Start/Stop toggle
|
||||
fun setPlayButtonEnabled(@Suppress("UNUSED_PARAMETER") enabled: Boolean) {
|
||||
binding.btnPlay.isEnabled = true
|
||||
}
|
||||
|
||||
fun setRtmpButtonText(isPlaying: Boolean) {
|
||||
binding.btnPlay.text = if (isPlaying) "停止播放(RTMP)" else "开始播放(RTMP)"
|
||||
}
|
||||
|
||||
fun setWhepButtonText(isWhepPlaying: Boolean) {
|
||||
binding.btnWhepPlay.text = if (isWhepPlaying) "停止播放(WHEP)" else "开始播放(WHEP)"
|
||||
}
|
||||
|
||||
fun setPushPreviewHeader(mode: String) {
|
||||
binding.tvPushPreviewHeader.text = "📹 推流预览($mode)"
|
||||
}
|
||||
|
||||
fun clearSurface(holder: SurfaceHolder) {
|
||||
try {
|
||||
val canvas = holder.lockCanvas()
|
||||
if (canvas != null) {
|
||||
canvas.drawColor(Color.BLACK)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
8
example/src/main/res/anim/av_sheet_in.xml
Normal file
8
example/src/main/res/anim/av_sheet_in.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:duration="220"
|
||||
android:fromYDelta="100%p"
|
||||
android:toYDelta="0%p"
|
||||
android:interpolator="@android:interpolator/decelerate_quad" />
|
||||
</set>
|
||||
8
example/src/main/res/anim/av_sheet_out.xml
Normal file
8
example/src/main/res/anim/av_sheet_out.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:duration="180"
|
||||
android:fromYDelta="0%p"
|
||||
android:toYDelta="100%p"
|
||||
android:interpolator="@android:interpolator/accelerate_quad" />
|
||||
</set>
|
||||
5
example/src/main/res/color/av_segment_text.xml
Normal file
5
example/src/main/res/color/av_segment_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/av_text_primary" android:state_checked="true" />
|
||||
<item android:color="@color/av_text_secondary" />
|
||||
</selector>
|
||||
5
example/src/main/res/color/av_tab_text.xml
Normal file
5
example/src/main/res/color/av_tab_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/brand_primary" android:state_selected="true" />
|
||||
<item android:color="@color/av_tab_inactive" />
|
||||
</selector>
|
||||
5
example/src/main/res/drawable/bg_av_control_bar.xml
Normal file
5
example/src/main/res/drawable/bg_av_control_bar.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_stats_bg" />
|
||||
<corners android:radius="@dimen/av_corner_large" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_av_dialog_card.xml
Normal file
5
example/src/main/res/drawable/bg_av_dialog_card.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<corners android:radius="@dimen/av_corner_large" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_av_dialog_card_gray.xml
Normal file
5
example/src/main/res/drawable/bg_av_dialog_card_gray.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_modal_fill_gray" />
|
||||
<corners android:radius="@dimen/av_corner_large" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_av_home_button.xml
Normal file
5
example/src/main/res/drawable/bg_av_home_button.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/brand_primary" />
|
||||
<corners android:radius="@dimen/av_corner_large" />
|
||||
</shape>
|
||||
4
example/src/main/res/drawable/bg_av_icon_circle.xml
Normal file
4
example/src/main/res/drawable/bg_av_icon_circle.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
</shape>
|
||||
8
example/src/main/res/drawable/bg_av_input_field.xml
Normal file
8
example/src/main/res/drawable/bg_av_input_field.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/av_input_border" />
|
||||
<corners android:radius="@dimen/av_corner_small" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_primary_disabled" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/brand_primary" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_primary_pressed" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_button_red_disabled" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_button_red" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_button_red_pressed" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
12
example/src/main/res/drawable/bg_av_segment_checked_left.xml
Normal file
12
example/src/main/res/drawable/bg_av_segment_checked_left.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/av_input_border" />
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/av_corner_small"
|
||||
android:bottomLeftRadius="@dimen/av_corner_small"
|
||||
android:topRightRadius="0dp"
|
||||
android:bottomRightRadius="0dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/av_input_border" />
|
||||
</shape>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/av_input_border" />
|
||||
<corners
|
||||
android:topLeftRadius="0dp"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:topRightRadius="@dimen/av_corner_small"
|
||||
android:bottomRightRadius="@dimen/av_corner_small" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_segment_bg" />
|
||||
<corners android:radius="@dimen/av_corner_small" />
|
||||
</shape>
|
||||
9
example/src/main/res/drawable/bg_av_sheet.xml
Normal file
9
example/src/main/res/drawable/bg_av_sheet.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_card_fill" />
|
||||
<corners
|
||||
android:topLeftRadius="18dp"
|
||||
android:topRightRadius="18dp"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_av_stats_panel.xml
Normal file
5
example/src/main/res/drawable/bg_av_stats_panel.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_stats_bg" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
4
example/src/main/res/drawable/bg_live_action_circle.xml
Normal file
4
example/src/main/res/drawable/bg_live_action_circle.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/av_action_circle_bg" />
|
||||
</shape>
|
||||
4
example/src/main/res/drawable/bg_live_dot.xml
Normal file
4
example/src/main/res/drawable/bg_live_dot.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/av_stats_red" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_live_duration_badge.xml
Normal file
5
example/src/main/res/drawable/bg_live_duration_badge.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="#80000000" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
4
example/src/main/res/drawable/bg_live_play_button.xml
Normal file
4
example/src/main/res/drawable/bg_live_play_button.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#80FFFFFF" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_live_preview_mask.xml
Normal file
5
example/src/main/res/drawable/bg_live_preview_mask.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_live_protocol_tag.xml
Normal file
5
example/src/main/res/drawable/bg_live_protocol_tag.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/brand_primary" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_login_input_field.xml
Normal file
5
example/src/main/res/drawable/bg_login_input_field.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/av_segment_bg" />
|
||||
<corners android:radius="@dimen/av_corner_medium" />
|
||||
</shape>
|
||||
5
example/src/main/res/drawable/bg_login_logo.xml
Normal file
5
example/src/main/res/drawable/bg_login_logo.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@color/brand_primary" />
|
||||
<corners android:radius="@dimen/av_corner_large" />
|
||||
</shape>
|
||||
10
example/src/main/res/drawable/ic_av_camera.xml
Normal file
10
example/src/main/res/drawable/ic_av_camera.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M20,5h-3.17l-1.84,-2H9.01L7.17,5H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2zM20,19H4V7h4.05l1.83,-2h4.25l1.83,2H20v12zM12,9c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_close.xml
Normal file
10
example/src/main/res/drawable/ic_av_close.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19,6.41 17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_conference.xml
Normal file
10
example/src/main/res/drawable/ic_av_conference.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5C15,14.17 10.33,13 8,13zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_live_pull.xml
Normal file
10
example/src/main/res/drawable/ic_av_live_pull.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM21,19H3V5h18v14zM11,16l5,-3 -5,-3v6z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_live_push.xml
Normal file
10
example/src/main/res/drawable/ic_av_live_push.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,11c-1.1,0 -2,0.9 -2,2 0,1.1 0.9,2 2,2 1.1,0 2,-0.9 2,-2 0,-1.1 -0.9,-2 -2,-2zM12,2C7.03,2 3,6.03 3,11c0,3.87 2.45,7.19 5.88,8.54l1.5,-2.6C7.82,15.76 6,13.54 6,11c0,-3.31 2.69,-6 6,-6s6,2.69 6,6c0,2.54 -1.82,4.76 -4.38,5.94l1.5,2.6C18.55,18.19 21,14.87 21,11c0,-4.97 -4.03,-9 -9,-9z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_play.xml
Normal file
10
example/src/main/res/drawable/ic_av_play.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_replay_10.xml
Normal file
10
example/src/main/res/drawable/ic_av_replay_10.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.96 -0.7,2.8l1.46,1.46C19.54,16.46 20,14.79 20,13c0,-4.42 -3.58,-8 -8,-8zM11,11v5H9v-3H7v3c0,1.1 0.9,2 2,2h2c1.1,0 2,-0.9 2,-2v-5c0,-1.1 -0.9,-2 -2,-2H9c-1.1,0 -2,0.9 -2,2v1h2v-1h2z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_single_chat.xml
Normal file
10
example/src/main/res/drawable/ic_av_single_chat.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M17,10.5V7c0,-1.1 -0.9,-2 -2,-2H5C3.9,5 3,5.9 3,7v10c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-3.5l4,4v-11l-4,4z" />
|
||||
</vector>
|
||||
9
example/src/main/res/drawable/ic_av_tab_call.xml
Normal file
9
example/src/main/res/drawable/ic_av_tab_call.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.56,15.3 18.78,15.5 20,15.5C20.55,15.5 21,15.95 21,16.5V20C21,20.55 20.55,21 20,21C10.61,21 3,13.39 3,4C3,3.45 3.45,3 4,3H7.5C8.05,3 8.5,3.45 8.5,4C8.5,5.22 8.7,6.44 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_tab_home.xml
Normal file
10
example/src/main/res/drawable/ic_av_tab_home.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_tab_settings.xml
Normal file
10
example/src/main/res/drawable/ic_av_tab_settings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19.14,12.94c0.04,-0.31 0.06,-0.63 0.06,-0.94 0,-0.32 -0.02,-0.63 -0.06,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.11,-0.2 -0.36,-0.28 -0.57,-0.2l-2.39,0.96c-0.5,-0.38 -1.04,-0.7 -1.62,-0.94L14.96,2.5c-0.03,-0.21 -0.21,-0.36 -0.43,-0.36h-3.84c-0.22,0 -0.4,0.15 -0.43,0.36l-0.36,2.54c-0.58,0.24 -1.12,0.56 -1.62,0.94l-2.39,-0.96c-0.21,-0.08 -0.46,0 -0.57,0.2l-1.92,3.32c-0.11,0.2 -0.06,0.47 0.12,0.61l2.03,1.58c-0.04,0.31 -0.06,0.62 -0.06,0.94 0,0.31 0.02,0.63 0.06,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.11,0.2 0.36,0.28 0.57,0.2l2.39,-0.96c0.5,0.38 1.04,0.7 1.62,0.94l0.36,2.54c0.03,0.21 0.21,0.36 0.43,0.36h3.84c0.22,0 0.4,-0.15 0.43,-0.36l0.36,-2.54c0.58,-0.24 1.12,-0.56 1.62,-0.94l2.39,0.96c0.21,0.08 0.46,0 0.57,-0.2l1.92,-3.32c0.11,-0.2 0.06,-0.47 -0.12,-0.61l-2.03,-1.58zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_av_volume.xml
Normal file
10
example/src/main/res/drawable/ic_av_volume.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M3,9v6h4l5,5V4L7,9H3z" />
|
||||
</vector>
|
||||
74
example/src/main/res/drawable/ic_launcher_background.xml
Normal file
74
example/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
13
example/src/main/res/drawable/ic_live_beauty.xml
Normal file
13
example/src/main/res/drawable/ic_live_beauty.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2l1.7,5.1L19,9l-5.3,1.7L12,16l-1.7-5.3L5,9l5.3-1.9L12,2z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M18.5,3l0.7,2.1L21.5,6l-2.3,0.7L18.5,9l-0.7-2.3L15.5,6l2.3-0.9L18.5,3z" />
|
||||
</vector>
|
||||
11
example/src/main/res/drawable/ic_live_chevron_up.xml
Normal file
11
example/src/main/res/drawable/ic_live_chevron_up.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M7.41,15.41 12,10.83 16.59,15.41 18,14 12,8 6,14z" />
|
||||
</vector>
|
||||
|
||||
18
example/src/main/res/drawable/ic_live_image.xml
Normal file
18
example/src/main/res/drawable/ic_live_image.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M4,5c0,-1.105 0.895,-2 2,-2h12c1.105,0 2,0.895 2,2v14c0,1.105 -0.895,2 -2,2H6c-1.105,0 -2,-0.895 -2,-2V5zM6,5h12v14H6V5z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M8.5,11.5l2.5,3 1.5,-2 3.5,4H8z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M10,9a1.25,1.25 0,1 0,0.01 0z" />
|
||||
</vector>
|
||||
|
||||
14
example/src/main/res/drawable/ic_live_mic.xml
Normal file
14
example/src/main/res/drawable/ic_live_mic.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3s-3,1.34 -3,3v6c0,1.66 1.34,3 3,3z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,11h-2c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3H7c0,2.76 2.24,5 5,5v3h2v-3c2.76,0 5,-2.24 5,-5z" />
|
||||
</vector>
|
||||
|
||||
14
example/src/main/res/drawable/ic_live_mic_off.xml
Normal file
14
example/src/main/res/drawable/ic_live_mic_off.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M16.5,11c0,0.36 -0.06,0.71 -0.16,1.04l-1.48,-1.48c0.09,-0.18 0.14,-0.37 0.14,-0.56V5c0,-1.66 -1.34,-3 -3,-3 -0.64,0 -1.23,0.2 -1.72,0.54l1.37,1.37C11.78,3.73 11.89,3.5 12,3.5c0.83,0 1.5,0.67 1.5,1.5v6c0,0.11 -0.02,0.22 -0.05,0.32L16.5,11z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M4,3l17,17 -1.41,1.41 -5.09,-5.09V19h-2v-3c-2.76,0 -5,-2.24 -5,-5h2c0,1.66 1.34,3 3,3 0.49,0 0.95,-0.12 1.35,-0.33L5.41,4.41 4,3z" />
|
||||
</vector>
|
||||
|
||||
11
example/src/main/res/drawable/ic_live_orientation.xml
Normal file
11
example/src/main/res/drawable/ic_live_orientation.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M7,2h10a2,2 0 0 1 2,2v16a2,2 0 0 1 -2,2H7a2,2 0 0 1 -2,-2V4a2,2 0 0 1 2,-2zM9,4h6v16H9z" />
|
||||
</vector>
|
||||
11
example/src/main/res/drawable/ic_live_stats.xml
Normal file
11
example/src/main/res/drawable/ic_live_stats.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M4,20h16v-2H4zM6,17h3V10H6zM11,17h3V6h-3zM16,17h3V13h-3z" />
|
||||
</vector>
|
||||
|
||||
15
example/src/main/res/drawable/ic_live_stats_cpu.xml
Normal file
15
example/src/main/res/drawable/ic_live_stats_cpu.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M9,9h6v6H9z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M7,7h10v10H7zM5,5v2H3v2h2v6H3v2h2v2h2v-2h10v2h2v-2h2v-2h-2V9h2V7h-2V5h-2v2H7V5H5z" />
|
||||
</vector>
|
||||
|
||||
15
example/src/main/res/drawable/ic_live_stats_fps.xml
Normal file
15
example/src/main/res/drawable/ic_live_stats_fps.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M4,6c0,-1.105 0.895,-2 2,-2h12c1.105,0 2,0.895 2,2v12c0,1.105 -0.895,2 -2,2H6c-1.105,0 -2,-0.895 -2,-2V6zM6,6h12v12H6V6z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M8,9h8v2H8zM8,13h5v2H8z" />
|
||||
</vector>
|
||||
|
||||
14
example/src/main/res/drawable/ic_live_stats_kbps.xml
Normal file
14
example/src/main/res/drawable/ic_live_stats_kbps.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9zM12,19c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M11,12l6,-3 -3,6z" />
|
||||
</vector>
|
||||
|
||||
18
example/src/main/res/drawable/ic_live_stats_protocol.xml
Normal file
18
example/src/main/res/drawable/ic_live_stats_protocol.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M12,2C6.477,2 2,6.477 2,12s4.477,10 10,10 10,-4.477 10,-10S17.523,2 12,2zM4,12c0,-4.418 3.582,-8 8,-8s8,3.582 8,8 -3.582,8 -8,8 -8,-3.582 -8,-8z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,4c2.21,0 4,3.582 4,8s-1.79,8 -4,8 -4,-3.582 -4,-8 1.79,-8 4,-8z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M4.5,12h15v1.5h-15z" />
|
||||
</vector>
|
||||
|
||||
14
example/src/main/res/drawable/ic_live_stats_rtt.xml
Normal file
14
example/src/main/res/drawable/ic_live_stats_rtt.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2c-5.52,0 -10,4.48 -10,10s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,6h-1.2v6l5.2,3.1 0.6,-1 -4.6,-2.7z" />
|
||||
</vector>
|
||||
|
||||
20
example/src/main/res/drawable/ic_live_switch_camera.xml
Normal file
20
example/src/main/res/drawable/ic_live_switch_camera.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20,6h-3.17L15,4H9L7.17,6H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zM12,19c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,9.5c1.93,0 3.5,1.57 3.5,3.5S13.93,16.5 12,16.5 8.5,14.93 8.5,13 10.07,9.5 12,9.5z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M7,12h2l-2.5,-2.5L4,12h2c0,2.21 1.79,4 4,4v-2c-1.1,0 -2,-0.9 -2,-2z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,12c0,-2.21 -1.79,-4 -4,-4v2c1.1,0 2,0.9 2,2h-2l2.5,2.5L20,12z" />
|
||||
</vector>
|
||||
|
||||
11
example/src/main/res/drawable/ic_live_video.xml
Normal file
11
example/src/main/res/drawable/ic_live_video.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,10.5V7c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-3.5l4,4v-11l-4,4z" />
|
||||
</vector>
|
||||
|
||||
14
example/src/main/res/drawable/ic_live_video_off.xml
Normal file
14
example/src/main/res/drawable/ic_live_video_off.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M4,3l17,17 -1.41,1.41 -2.59,-2.59H5c-1.1,0 -2,-0.9 -2,-2V7c0,-0.9 0.6,-1.67 1.42,-1.91L2.59,3.41 4,3z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21,6.5v11l-4,-4v3.5c0,0.22 -0.04,0.43 -0.1,0.63L6.37,7.1C6.57,7.04 6.78,7 7,7h8c1.1,0 2,0.9 2,2v3.5l4,-4z" />
|
||||
</vector>
|
||||
|
||||
10
example/src/main/res/drawable/ic_login_eye.xml
Normal file
10
example/src/main/res/drawable/ic_login_eye.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,5C7,5 3.2,8.1 2,12c1.2,3.9 5,7 10,7s8.8,-3.1 10,-7c-1.2,-3.9 -5,-7 -10,-7zm0,12c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zm0,-8a3,3 0 1,0 0,6 3,3 0 0,0 0,-6z" />
|
||||
</vector>
|
||||
16
example/src/main/res/drawable/ic_login_eye_off.xml
Normal file
16
example/src/main/res/drawable/ic_login_eye_off.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,5C7,5 3.2,8.1 2,12c1.2,3.9 5,7 10,7s8.8,-3.1 10,-7c-1.2,-3.9 -5,-7 -10,-7zm0,12c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zm0,-8a3,3 0 1,0 0,6 3,3 0 0,0 0,-6z" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4,4 L20,20"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeWidth="2" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_login_lock.xml
Normal file
10
example/src/main/res/drawable/ic_login_lock.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,17a2,2 0 0,0 2,-2v-2a2,2 0 0,0 -4,0v2a2,2 0 0,0 2,2zm6,-8h-1V7a5,5 0 0,0 -10,0v2H6a2,2 0 0,0 -2,2v8a2,2 0 0,0 2,2h12a2,2 0 0,0 2,-2v-8a2,2 0 0,0 -2,-2zm-3,0H9V7a3,3 0 0,1 6,0v2z" />
|
||||
</vector>
|
||||
17
example/src/main/res/drawable/ic_login_logo.xml
Normal file
17
example/src/main/res/drawable/ic_login_logo.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/brand_primary"
|
||||
android:fillAlpha="0.75"
|
||||
android:pathData="M6,2 H14 A3,3 0 0,1 17,5 V11 A3,3 0 0,1 14,14 H6 A3,3 0 0,1 3,11 V5 A3,3 0 0,1 6,2 Z" />
|
||||
<path
|
||||
android:fillColor="@color/brand_primary"
|
||||
android:pathData="M10,6 H18 A3,3 0 0,1 21,9 V15 A3,3 0 0,1 18,18 H10 A3,3 0 0,1 7,15 V9 A3,3 0 0,1 10,6 Z" />
|
||||
<path
|
||||
android:fillColor="@color/brand_primary_text_on"
|
||||
android:pathData="M13,10 L13,14 L16.5,12 Z" />
|
||||
</vector>
|
||||
10
example/src/main/res/drawable/ic_login_user.xml
Normal file
10
example/src/main/res/drawable/ic_login_user.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zm0,2c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_av_primary_button_disabled" android:state_enabled="false" />
|
||||
<item android:drawable="@drawable/bg_av_primary_button_pressed" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/bg_av_primary_button_normal" />
|
||||
</selector>
|
||||
6
example/src/main/res/drawable/selector_av_red_button.xml
Normal file
6
example/src/main/res/drawable/selector_av_red_button.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_av_red_button_disabled" android:state_enabled="false" />
|
||||
<item android:drawable="@drawable/bg_av_red_button_pressed" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/bg_av_red_button_normal" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_av_segment_checked_left" android:state_checked="true" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_av_segment_checked_middle" android:state_checked="true" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_av_segment_checked_right" android:state_checked="true" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -3,150 +3,664 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="16dp">
|
||||
android:background="@color/av_background">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHubSubtitle"
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hub_subtitle_basic"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#101215"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/tab_home"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollCards"
|
||||
<FrameLayout
|
||||
android:id="@+id/pageContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/vTabDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||
|
||||
<!-- Home -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/pageHome"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="@dimen/av_screen_padding_home"
|
||||
android:paddingEnd="@dimen/av_screen_padding_home"
|
||||
android:paddingTop="18dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/homeActionRow"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/av_home_button_height"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btnHomeLivePush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_av_home_button"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:contentDescription="@string/home_live_push"
|
||||
android:src="@drawable/ic_av_live_push"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/home_live_push"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btnHomeLivePull"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_av_home_button"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:contentDescription="@string/home_live_pull"
|
||||
android:src="@drawable/ic_av_live_pull"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/home_live_pull"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHomeSquareTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/home_live_square_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/homeActionRow" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeHome"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHomeSquareTitle">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvLiveSquare"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="12dp"
|
||||
android:overScrollMode="never" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHomeEmpty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/home_live_square_empty"
|
||||
android:textColor="@color/av_text_hint"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Call -->
|
||||
<ScrollView
|
||||
android:id="@+id/pageCall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/av_screen_padding"
|
||||
android:paddingTop="28dp"
|
||||
android:paddingEnd="@dimen/av_screen_padding"
|
||||
android:paddingBottom="28dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/call_channel_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCallChannelId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:hint="@string/default_call_id"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btnCallSingleChat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_home_button_height"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/bg_av_home_button"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:contentDescription="@string/home_single_chat"
|
||||
android:src="@drawable/ic_av_single_chat"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/home_single_chat"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btnCallConference"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_home_button_height"
|
||||
android:layout_marginTop="14dp"
|
||||
android:background="@drawable/bg_av_home_button"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:contentDescription="@string/home_conference"
|
||||
android:src="@drawable/ic_av_conference"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/home_conference"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Settings -->
|
||||
<ScrollView
|
||||
android:id="@+id/pageSettings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/av_screen_padding"
|
||||
android:paddingTop="18dp"
|
||||
android:paddingEnd="@dimen/av_screen_padding"
|
||||
android:paddingBottom="28dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/push_settings_stream_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsStreamId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/settings_resolution"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgSettingsResolution"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_segment_container"
|
||||
android:checkedButton="@+id/rbSettingsRes720p"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSettingsRes360p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_left"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="360p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSettingsRes480p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="480p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSettingsRes540p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="540p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSettingsRes720p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_right"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="720p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/settings_fps"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsFps"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="30"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/settings_max_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsMaxBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="2000"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/settings_min_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsMinBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="500"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/settings_env_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/settings_vhost"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsVhost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textUri"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_vhost_key"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsVhostKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_app_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSettingsAppId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveSettings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/selector_av_primary_button"
|
||||
android:text="@string/settings_save"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLogout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/selector_av_red_button"
|
||||
android:text="@string/logout_action"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/vTabDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/av_divider"
|
||||
app:layout_constraintBottom_toTopOf="@id/tabBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tabBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/av_tab_height"
|
||||
android:background="@color/av_background"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHubSubtitle"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:id="@+id/tabHome"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardLiveStreaming"
|
||||
android:layout_width="match_parent"
|
||||
<ImageView
|
||||
android:id="@+id/ivTabHome"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/tab_home"
|
||||
android:duplicateParentState="true"
|
||||
android:src="@drawable/ic_av_tab_home"
|
||||
app:tint="@color/av_tab_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTabHome"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="@color/brand_primary"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="64dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/live_streaming_title"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="@string/live_streaming_subtitle"
|
||||
android:textColor="@color/brand_primary_text_sub"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:contentDescription="@null"
|
||||
app:tint="#E6FFFFFF" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardInteractiveLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="@color/brand_primary"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="64dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/interactive_live_title"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="@string/interactive_live_subtitle"
|
||||
android:textColor="@color/brand_primary_text_sub"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:contentDescription="@null"
|
||||
app:tint="#E6FFFFFF" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:layout_marginTop="2dp"
|
||||
android:duplicateParentState="true"
|
||||
android:text="@string/tab_home"
|
||||
android:textColor="@color/av_tab_text"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tabCall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivTabCall"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/tab_call"
|
||||
android:duplicateParentState="true"
|
||||
android:src="@drawable/ic_av_tab_call"
|
||||
app:tint="@color/av_tab_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTabCall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:duplicateParentState="true"
|
||||
android:text="@string/tab_call"
|
||||
android:textColor="@color/av_tab_text"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tabSettings"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivTabSettings"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/tab_settings"
|
||||
android:duplicateParentState="true"
|
||||
android:src="@drawable/ic_av_tab_settings"
|
||||
app:tint="@color/av_tab_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTabSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:duplicateParentState="true"
|
||||
android:text="@string/tab_settings"
|
||||
android:textColor="@color/av_tab_text"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
162
example/src/main/res/layout/activity_live_play.xml
Normal file
162
example/src/main/res/layout/activity_live_play.xml
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/av_surface_black">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/renderContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/bg_av_icon_circle"
|
||||
android:contentDescription="@string/close"
|
||||
android:src="@drawable/ic_av_close"
|
||||
app:tint="@color/av_text_primary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controlBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/av_control_bar_height"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:background="@drawable/bg_av_control_bar"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionPlay"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/play_ctrl_play"
|
||||
android:src="@drawable/ic_av_play"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPlayLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/play_ctrl_play"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionMute"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/play_ctrl_mute"
|
||||
android:src="@drawable/ic_av_volume"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMuteLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/play_ctrl_mute"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionScreenshot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/play_ctrl_screenshot"
|
||||
android:src="@drawable/ic_av_camera"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/play_ctrl_screenshot"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionSeek10"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/play_ctrl_seek_10"
|
||||
android:src="@drawable/ic_av_replay_10"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/play_ctrl_seek_10"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
523
example/src/main/res/layout/activity_live_push.xml
Normal file
523
example/src/main/res/layout/activity_live_push.xml
Normal file
@@ -0,0 +1,523 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/av_surface_black">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/previewContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/statsPanel"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_av_stats_panel"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivStatsTitleIcon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="@string/live_stats_title"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ivStatsCollapse"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:background="@drawable/bg_live_action_circle"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_chevron_up"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:alpha="0.12"
|
||||
android:background="@color/brand_primary_text_on" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/statsContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_protocol"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsProtocol"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="rtmp"
|
||||
android:textColor="@color/brand_primary_text_sub"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_cpu"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsCpuApp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="App CPU: 0%"
|
||||
android:textColor="@color/av_stats_green"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_cpu"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsCpuSys"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Sys CPU: 0%"
|
||||
android:textColor="@color/av_stats_green"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_kbps"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsKbps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="0 kbps (A:0 V:0)"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_fps"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsFps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="FPS: 0"
|
||||
android:textColor="@color/av_stats_red"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_live_stats_rtt"
|
||||
app:tint="@color/brand_primary_text_sub" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatsRtt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="RTT: 0 ms"
|
||||
android:textColor="@color/av_stats_green"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/topRightActions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPushSettings"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/bg_live_action_circle"
|
||||
android:contentDescription="@string/settings_title"
|
||||
android:src="@drawable/ic_av_tab_settings"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/bg_live_action_circle"
|
||||
android:contentDescription="@string/close"
|
||||
android:src="@drawable/ic_av_close"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/bottomAnchor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controlBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/av_control_bar_height"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:background="@drawable/bg_av_control_bar"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionFlip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_flip"
|
||||
android:src="@drawable/ic_live_switch_camera"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_flip"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionMute"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivToolMute"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_mute"
|
||||
android:src="@drawable/ic_live_mic"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToolMuteLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_mute"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionCamera"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivToolCamera"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_camera_off"
|
||||
android:src="@drawable/ic_live_video"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToolCameraLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_camera_off"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionBeauty"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivToolBeauty"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_beauty_off"
|
||||
android:src="@drawable/ic_live_beauty"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToolBeautyLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_beauty_off"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionScreenshot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_screenshot"
|
||||
android:src="@drawable/ic_av_camera"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_screenshot"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionBackground"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivToolBackground"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/push_tool_background"
|
||||
android:src="@drawable/ic_live_image"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/push_tool_background"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bottomBarrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierAllowsGoneWidgets="false"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="controlBar,bottomAnchor" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomStartRow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="26dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStartStopLive"
|
||||
android:layout_width="220dp"
|
||||
android:layout_height="52dp"
|
||||
android:background="@drawable/selector_av_red_button"
|
||||
android:text="@string/push_start_live"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/quickActions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnQuickFlip"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:background="@drawable/bg_live_action_circle"
|
||||
android:contentDescription="@string/push_tool_flip"
|
||||
android:src="@drawable/ic_live_switch_camera"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnQuickOrientation"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="@drawable/bg_live_action_circle"
|
||||
android:contentDescription="@string/push_stream_orientation"
|
||||
android:src="@drawable/ic_live_orientation"
|
||||
app:tint="@color/brand_primary_text_on" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
131
example/src/main/res/layout/activity_login.xml
Normal file
131
example/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/av_background">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loginContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/av_screen_padding"
|
||||
android:paddingEnd="@dimen/av_screen_padding"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="83dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/login_logo_desc"
|
||||
android:src="@drawable/ic_login_logo" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/login_welcome"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/bg_login_input_field"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_login_user"
|
||||
app:tint="@color/av_text_hint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etUsername"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:hint="@string/login_username_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_login_input_field"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_login_lock"
|
||||
app:tint="@color/av_text_hint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPassword"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:hint="@string/login_password_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:imeOptions="actionDone"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnTogglePassword"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/login_toggle_password"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_login_eye_off"
|
||||
app:tint="@color/av_text_hint" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/selector_av_primary_button"
|
||||
android:text="@string/login_action"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,576 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000">
|
||||
|
||||
<!-- 顶部:推流行(输入框 + 按钮列) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/pushRow"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<!-- 推流配置输入区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:background="#333333"
|
||||
android:padding="4dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#555555"
|
||||
android:layout_marginVertical="1dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAppName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="App Name (如: live)"
|
||||
android:text="live"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#CCCCCC"
|
||||
android:textSize="11sp"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no"
|
||||
android:padding="2dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#555555"
|
||||
android:layout_marginVertical="1dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="Stream Name (如: stream123)"
|
||||
android:text="stream001"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#CCCCCC"
|
||||
android:textSize="11sp"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no"
|
||||
android:padding="2dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#555555"
|
||||
android:layout_marginVertical="1dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pushButtonContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="2dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStartPush"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="120dp"
|
||||
android:layout_height="32dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#4CAF50"
|
||||
android:text="开始推流"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStopPush"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="120dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#F44336"
|
||||
android:text="停止推流"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 新增:协议选择(RTMP / WHIP) -->
|
||||
<RadioGroup
|
||||
android:id="@+id/protocolGroup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushRow">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbProtocolRtmp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RTMP"
|
||||
android:checked="true"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbProtocolWhip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="WHIP"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
</RadioGroup>
|
||||
|
||||
<!-- 推流控制:放在协议选择下方 -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/pushControlsContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/protocolGroup">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/resolutionScrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnSwitchCamera"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/resolutionGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/res360p"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:text="360p"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/res540p"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:text="540p"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/res720p"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:checked="true"
|
||||
android:text="720p"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
<RadioButton
|
||||
android:id="@+id/res1080p"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:text="1080p"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
</RadioGroup>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSwitchCamera"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="110dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#FF9800"
|
||||
android:text="切换摄像头"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnSwitchOrientation"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="wrap" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSwitchOrientation"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="110dp"
|
||||
android:layout_height="32dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#03A9F4"
|
||||
android:text="切换方向"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="wrap" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- 镜像控制:精简为两个统一镜像(同时作用预览与推流) + 美颜 -->
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/mirrorScrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushControlsContainer">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbPreviewHFlip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:text="水平镜像"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbPreviewVFlip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="垂直镜像"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switchBeauty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="美颜"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp"
|
||||
android:checked="true"
|
||||
app:thumbTint="#4CAF50"
|
||||
app:trackTint="#81C784" />
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<!-- 播放行(输入框 + 按钮列):放在镜像控制下方 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/playRow"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/mirrorScrollView">
|
||||
|
||||
<!-- 播放配置输入区域 -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:background="#333333"
|
||||
android:padding="4dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPlayAppName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="Play App Name (如: live)"
|
||||
android:text="live"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#CCCCCC"
|
||||
android:textSize="11sp"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no"
|
||||
android:padding="2dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#555555"
|
||||
android:layout_marginVertical="1dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPlayStreamName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="Play Stream Name (如: stream123)"
|
||||
android:text="stream001"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#CCCCCC"
|
||||
android:textSize="11sp"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no"
|
||||
android:padding="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playButtonContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="2dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="140dp"
|
||||
android:layout_height="32dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#2196F3"
|
||||
android:text="开始播放(RTMP)"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnWhepPlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="140dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#E91E63"
|
||||
android:text="开始播放(WHEP)"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 功能按钮区域 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/functionButtonsLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/playRow">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnChooseImageSource"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:text="图片作为视频源"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
android:backgroundTint="#E91E63" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRestoreCamera"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:backgroundTint="#607D8B"
|
||||
android:text="恢复摄像头源"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 新增:截图按钮行 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/screenshotButtonsLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/functionButtonsLayout">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCapturePush"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:text="截图(推流预览)"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
android:backgroundTint="#607D8B" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCapturePlay"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="1dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:maxLines="1"
|
||||
android:text="截图(播放)"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
android:backgroundTint="#607D8B" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
|
||||
<!-- 视频显示区域:剩余空间自适应;设置最小高度 -->
|
||||
<TextView
|
||||
android:id="@+id/tvStatus"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="#80000000"
|
||||
android:padding="4dp"
|
||||
android:text="推流状态: 待启动 | 播放状态: 待启动"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/screenshotButtonsLayout" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/videoContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2"
|
||||
android:baselineAligned="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvStatus">
|
||||
|
||||
<!-- 推流预览 -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#222222"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPushPreviewHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#555555"
|
||||
android:gravity="center"
|
||||
android:padding="3dp"
|
||||
android:text="📹 推流预览"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.pedro.library.view.OpenGlView
|
||||
android:id="@+id/surfaceViewPush"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="160dp" />
|
||||
|
||||
<!-- 新增:WHIP 预览渲染器,按需显示 -->
|
||||
<org.webrtc.SurfaceViewRenderer
|
||||
android:id="@+id/whipPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 拉流播放 -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#222222"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#555555"
|
||||
android:gravity="center"
|
||||
android:padding="3dp"
|
||||
android:text="📺 拉流播放"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surfaceViewPlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="160dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etNewUrl"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="输入RTMP地址或留空使用Kiwi"
|
||||
android:padding="12dp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#CCCCCC"
|
||||
android:background="#333333"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnAddStream"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAddStream"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="添加"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#4CAF50"
|
||||
app:layout_constraintTop_toTopOf="@id/etNewUrl"
|
||||
app:layout_constraintBottom_toBottomOf="@id/etNewUrl"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnStartAll"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStartAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="全部开始"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#2196F3"
|
||||
app:layout_constraintTop_toTopOf="@id/etNewUrl"
|
||||
app:layout_constraintBottom_toBottomOf="@id/etNewUrl"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnStopAll"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStopAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="全部停止"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#F44336"
|
||||
app:layout_constraintTop_toTopOf="@id/etNewUrl"
|
||||
app:layout_constraintBottom_toBottomOf="@id/etNewUrl"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollStreams"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/etNewUrl"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/streamsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
212
example/src/main/res/layout/dialog_live_preset_settings.xml
Normal file
212
example/src/main/res/layout/dialog_live_preset_settings.xml
Normal file
@@ -0,0 +1,212 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/av_screen_padding">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_av_dialog_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingBottom="18dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/settings_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/push_settings_close"
|
||||
android:src="@drawable/ic_av_close"
|
||||
app:tint="@color/brand_primary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/push_settings_stream_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_resolution"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgResolution"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_segment_container"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes360p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_left"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="360p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes480p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="480p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes540p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="540p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes720p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_right"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="720p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_fps"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFps"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_max_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMaxBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_min_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMinBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApply"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="18dp"
|
||||
android:background="@drawable/selector_av_primary_button"
|
||||
android:text="@string/push_settings_apply"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
290
example/src/main/res/layout/dialog_live_push_settings.xml
Normal file
290
example/src/main/res/layout/dialog_live_push_settings.xml
Normal file
@@ -0,0 +1,290 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/av_screen_padding">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_av_dialog_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingBottom="18dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/settings_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/push_settings_close"
|
||||
android:src="@drawable/ic_av_close"
|
||||
app:tint="@color/brand_primary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/push_settings_stream_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_resolution"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgResolution"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_segment_container"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes360p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_left"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="360p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes480p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="480p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes540p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_middle"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="540p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRes720p"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_right"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="720p"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_fps"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFps"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_max_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMaxBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/settings_min_bitrate"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMinBitrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/settings_env_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/settings_vhost"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etEnvVhost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textUri"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/settings_vhost_key"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etEnvVhostKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/settings_app_id"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etEnvAppId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApply"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="18dp"
|
||||
android:background="@drawable/selector_av_primary_button"
|
||||
android:text="@string/push_settings_apply"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
118
example/src/main/res/layout/dialog_play_config_overlay.xml
Normal file
118
example/src/main/res/layout/dialog_play_config_overlay.xml
Normal file
@@ -0,0 +1,118 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/av_overlay_dim">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/bg_av_icon_circle"
|
||||
android:contentDescription="@string/close"
|
||||
android:src="@drawable/ic_av_close"
|
||||
app:tint="@color/av_text_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/card"
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_av_dialog_card_gray"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingBottom="18dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/play_config_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/play_config_protocol_label"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgProtocol"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_segment_container"
|
||||
android:checkedButton="@+id/rbRtmp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRtmp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_left"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="@string/protocol_rtmp"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbRtc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/selector_av_segment_right"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:text="@string/protocol_rtc"
|
||||
android:textColor="@color/av_segment_text"
|
||||
android:textSize="14sp" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/play_config_stream_hint"
|
||||
android:textColor="@color/av_text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamIdOrUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_field_height"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_av_input_field"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="test"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textColorHint="@color/av_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStartPlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/av_primary_button_height"
|
||||
android:layout_marginTop="18dp"
|
||||
android:background="@drawable/selector_av_primary_button"
|
||||
android:text="@string/play_start"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
88
example/src/main/res/layout/dialog_push_protocol_sheet.xml
Normal file
88
example/src/main/res/layout/dialog_push_protocol_sheet.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_av_dialog_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/push_protocol_sheet_title"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/push_protocol_sheet_message"
|
||||
android:textColor="@color/av_text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/av_divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnRtc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:text="@string/push_protocol_rtc"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/av_divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnRtmp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:text="@string/push_protocol_rtmp"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/bg_av_dialog_card"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
119
example/src/main/res/layout/item_live_square_card.xml
Normal file
119
example/src/main/res/layout/item_live_square_card.xml
Normal file
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="0dp"
|
||||
app:cardPreventCornerOverlap="false"
|
||||
app:cardUseCompatPadding="false">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/previewContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_live_preview_mask"
|
||||
android:clipToOutline="true"
|
||||
app:layout_constraintDimensionRatio="3:4"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/home_live_square"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivPlay"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_live_play_button"
|
||||
android:contentDescription="@string/play_ctrl_play"
|
||||
android:src="@drawable/ic_av_play"
|
||||
app:tint="@color/brand_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDuration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/bg_live_duration_badge"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:text="00:00"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/infoContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/previewContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStreamName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="stream001"
|
||||
android:textColor="@color/av_text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:background="@drawable/bg_live_dot" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text="@string/live_status"
|
||||
android:textColor="@color/av_stats_red"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProtocol"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/bg_live_protocol_tag"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:text="RTMP"
|
||||
android:textColor="@color/brand_primary_text_on"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#222222"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp"
|
||||
android:background="#444444"
|
||||
android:text="流"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="#000000">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surfaceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"/>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end"
|
||||
android:padding="8dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPrepare"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="准备"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#03A9F4"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="开始"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#2196F3"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="停止"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#F44336"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRemove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="移除"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#9E9E9E"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:padding="8dp"
|
||||
android:text="状态: 待添加"/>
|
||||
</LinearLayout>
|
||||
5
example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
5
example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
example/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
BIN
example/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
example/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
BIN
example/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
example/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
BIN
example/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
example/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
BIN
example/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user