添加画中画模式支持,优化直播播放体验
This commit is contained in:
@@ -56,9 +56,11 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".live.LivePlayActivity"
|
android:name=".live.LivePlayActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden"
|
||||||
android:screenOrientation="fullSensor"
|
android:screenOrientation="fullSensor"
|
||||||
android:theme="@style/Theme.AVDemo.NoActionBar"
|
android:theme="@style/Theme.AVDemo.NoActionBar"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
android:parentActivityName=".FeatureHubActivity" />
|
android:parentActivityName=".FeatureHubActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.ClipboardManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
@@ -44,6 +45,7 @@ import com.sellycloud.sellycloudsdk.SellyLatencyChasingUpdate
|
|||||||
import com.sellycloud.sellycloudsdk.SellyLiveMode
|
import com.sellycloud.sellycloudsdk.SellyLiveMode
|
||||||
import com.sellycloud.sellycloudsdk.SellyLiveVideoPlayer
|
import com.sellycloud.sellycloudsdk.SellyLiveVideoPlayer
|
||||||
import com.sellycloud.sellycloudsdk.SellyLiveVideoPlayerDelegate
|
import com.sellycloud.sellycloudsdk.SellyLiveVideoPlayerDelegate
|
||||||
|
import com.sellycloud.sellycloudsdk.SellyPipController
|
||||||
import com.sellycloud.sellycloudsdk.SellyPlayerState
|
import com.sellycloud.sellycloudsdk.SellyPlayerState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -64,6 +66,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
private lateinit var args: Args
|
private lateinit var args: Args
|
||||||
|
|
||||||
private lateinit var playerClient: SellyLiveVideoPlayer
|
private lateinit var playerClient: SellyLiveVideoPlayer
|
||||||
|
private lateinit var pipController: SellyPipController
|
||||||
|
|
||||||
private var isPlaying: Boolean = false
|
private var isPlaying: Boolean = false
|
||||||
private var isMuted: Boolean = false
|
private var isMuted: Boolean = false
|
||||||
@@ -82,6 +85,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
private var logDialog: AlertDialog? = null
|
private var logDialog: AlertDialog? = null
|
||||||
private var logSummaryView: TextView? = null
|
private var logSummaryView: TextView? = null
|
||||||
private var logContentView: TextView? = null
|
private var logContentView: TextView? = null
|
||||||
|
private var logFloatingButton: View? = null
|
||||||
|
|
||||||
private val storagePermissionLauncher = registerForActivityResult(
|
private val storagePermissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
@@ -99,6 +103,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
addLogFloatingButton()
|
addLogFloatingButton()
|
||||||
|
|
||||||
envStore = LiveEnvSettingsStore(this)
|
envStore = LiveEnvSettingsStore(this)
|
||||||
|
pipController = SellyPipController(this)
|
||||||
val env = envStore.read().also { it.applyToSdkRuntimeConfig(this) }
|
val env = envStore.read().also { it.applyToSdkRuntimeConfig(this) }
|
||||||
args = Args.from(intent, env)
|
args = Args.from(intent, env)
|
||||||
Log.d(TAG, "init liveMode=${args.liveMode} input=${args.streamIdOrUrl} autoStart=${args.autoStart}")
|
Log.d(TAG, "init liveMode=${args.liveMode} input=${args.streamIdOrUrl} autoStart=${args.autoStart}")
|
||||||
@@ -184,7 +189,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
binding.actionPlay.setOnClickListener { togglePlay() }
|
binding.actionPlay.setOnClickListener { togglePlay() }
|
||||||
binding.actionMute.setOnClickListener { toggleMute() }
|
binding.actionMute.setOnClickListener { toggleMute() }
|
||||||
binding.actionScreenshot.setOnClickListener { captureCurrentFrame() }
|
binding.actionScreenshot.setOnClickListener { captureCurrentFrame() }
|
||||||
binding.actionSeek10.setOnClickListener { seekForward10s() }
|
binding.actionPip.setOnClickListener { enterPipMode() }
|
||||||
|
|
||||||
playerClient.attachRenderView(binding.renderContainer)
|
playerClient.attachRenderView(binding.renderContainer)
|
||||||
|
|
||||||
@@ -203,6 +208,22 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
uiScope.cancel()
|
uiScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
if (isInPictureInPictureMode) return
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
if (!isPlaying) return
|
||||||
|
enterPipMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
||||||
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||||
|
updatePipUi(isInPictureInPictureMode)
|
||||||
|
}
|
||||||
|
|
||||||
private fun togglePlay() {
|
private fun togglePlay() {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
logEvent("用户操作: 暂停")
|
logEvent("用户操作: 暂停")
|
||||||
@@ -224,15 +245,11 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
logEvent(if (isMuted) "用户操作: 静音" else "用户操作: 取消静音")
|
logEvent(if (isMuted) "用户操作: 静音" else "用户操作: 取消静音")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun seekForward10s() {
|
private fun enterPipMode() {
|
||||||
logEvent("用户操作: 快进10秒")
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
if (args.liveMode == SellyLiveMode.RTC) {
|
if (!isPlaying) return
|
||||||
Toast.makeText(this, "RTC 暂不支持快进", Toast.LENGTH_SHORT).show()
|
val renderView = playerClient.getRenderView() ?: binding.renderContainer
|
||||||
return
|
pipController.enterPictureInPictureMode(renderView)
|
||||||
}
|
|
||||||
val ok = playerClient.seekBy(10_000L)
|
|
||||||
if (!ok) Toast.makeText(this, "当前流不支持快进", Toast.LENGTH_SHORT).show()
|
|
||||||
logEvent(if (ok) "快进结果: 成功" else "快进结果: 失败")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlayback() {
|
private fun startPlayback() {
|
||||||
@@ -327,10 +344,26 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePreviewVisibility() {
|
private fun updatePreviewVisibility() {
|
||||||
|
if (isInPictureInPictureMode) {
|
||||||
|
binding.ivPreview.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
val shouldShow = !previewImageUrl.isNullOrBlank() && !hasFirstVideoFrameRendered
|
val shouldShow = !previewImageUrl.isNullOrBlank() && !hasFirstVideoFrameRendered
|
||||||
binding.ivPreview.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
binding.ivPreview.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updatePipUi(isInPip: Boolean) {
|
||||||
|
val controlsVisibility = if (isInPip) View.GONE else View.VISIBLE
|
||||||
|
binding.controlBar.visibility = controlsVisibility
|
||||||
|
binding.btnClose.visibility = controlsVisibility
|
||||||
|
logFloatingButton?.visibility = controlsVisibility
|
||||||
|
if (isInPip) {
|
||||||
|
binding.ivPreview.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
updatePreviewVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun captureCurrentFrame() {
|
private fun captureCurrentFrame() {
|
||||||
logEvent("用户操作: 截图")
|
logEvent("用户操作: 截图")
|
||||||
val view = playerClient.getRenderView()
|
val view = playerClient.getRenderView()
|
||||||
@@ -425,6 +458,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
bottomMargin = marginBottomPx
|
bottomMargin = marginBottomPx
|
||||||
}
|
}
|
||||||
addContentView(button, params)
|
addContentView(button, params)
|
||||||
|
logFloatingButton = button
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLogDialog() {
|
private fun showLogDialog() {
|
||||||
|
|||||||
10
example/src/main/res/drawable/ic_av_pip.xml
Normal file
10
example/src/main/res/drawable/ic_av_pip.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="#FFFFFFFF"
|
||||||
|
android:pathData="M19,7H5c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9c0-1.1-0.9-2-2-2zm0,10h-6v-4h6v4z" />
|
||||||
|
</vector>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/actionSeek10"
|
android:id="@+id/actionPip"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -158,15 +158,15 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:contentDescription="@string/play_ctrl_seek_10"
|
android:contentDescription="@string/play_ctrl_pip"
|
||||||
android:src="@drawable/ic_av_replay_10"
|
android:src="@drawable/ic_av_pip"
|
||||||
app:tint="@color/brand_primary_text_on" />
|
app:tint="@color/brand_primary_text_on" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:text="@string/play_ctrl_seek_10"
|
android:text="@string/play_ctrl_pip"
|
||||||
android:textColor="@color/brand_primary_text_on"
|
android:textColor="@color/brand_primary_text_on"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
<string name="play_ctrl_mute">静音</string>
|
<string name="play_ctrl_mute">静音</string>
|
||||||
<string name="play_ctrl_unmute">取消静音</string>
|
<string name="play_ctrl_unmute">取消静音</string>
|
||||||
<string name="play_ctrl_screenshot">截图</string>
|
<string name="play_ctrl_screenshot">截图</string>
|
||||||
<string name="play_ctrl_seek_10">快进10秒</string>
|
<string name="play_ctrl_pip">画中画</string>
|
||||||
|
|
||||||
<string name="live_stats_title">直播数据</string>
|
<string name="live_stats_title">直播数据</string>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user