添加RTMP Payload XOR保护功能,更新相关UI和逻辑,支持可选的流加密

This commit is contained in:
2026-02-25 14:59:56 +08:00
parent d0b1678833
commit 6a3421fa4d
9 changed files with 157 additions and 16 deletions

Binary file not shown.

View File

@@ -344,6 +344,7 @@ class FeatureHubActivity : AppCompatActivity() {
dialogBinding.etFps.setText(current.fps.toString())
dialogBinding.etMaxBitrate.setText(current.maxBitrateKbps.toString())
dialogBinding.etMinBitrate.setText(current.minBitrateKbps.toString())
dialogBinding.etXorKey.setText(current.xorKeyHex)
dialogBinding.rgResolution.check(
when (current.resolution) {
AvDemoSettings.Resolution.P360 -> R.id.rbRes360p
@@ -384,12 +385,14 @@ class FeatureHubActivity : AppCompatActivity() {
else -> AvDemoSettings.Resolution.P720
}
val xorKey = dialogBinding.etXorKey.text?.toString()?.trim().orEmpty()
val updated = current.copy(
streamId = streamId,
resolution = res,
fps = fps,
maxBitrateKbps = maxKbps,
minBitrateKbps = minKbps
minBitrateKbps = minKbps,
xorKeyHex = xorKey
)
settingsStore.write(updated)
dialog.dismiss()
@@ -413,13 +416,14 @@ class FeatureHubActivity : AppCompatActivity() {
Toast.makeText(this, "请输入 Stream ID 或 URL", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val xorKey = dialogBinding.etXorKey.text?.toString()?.trim().orEmpty()
val liveMode = if (dialogBinding.rbRtc.isChecked) {
SellyLiveMode.RTC
} else {
SellyLiveMode.RTMP
}
dialog.dismiss()
startActivity(LivePlayActivity.createIntent(this, liveMode, input, autoStart = true))
startActivity(LivePlayActivity.createIntent(this, liveMode, input, autoStart = true, xorKeyHex = xorKey))
}
dialog.show()

View File

@@ -9,6 +9,7 @@ data class AvDemoSettings(
val fps: Int,
val maxBitrateKbps: Int,
val minBitrateKbps: Int,
val xorKeyHex: String = "",
) {
enum class Resolution { P360, P480, P540, P720 }
@@ -36,7 +37,8 @@ class AvDemoSettingsStore(context: Context) {
resolution = resolution,
fps = prefs.getInt(KEY_FPS, DEFAULT_FPS),
maxBitrateKbps = prefs.getInt(KEY_MAX_KBPS, DEFAULT_MAX_KBPS),
minBitrateKbps = prefs.getInt(KEY_MIN_KBPS, DEFAULT_MIN_KBPS)
minBitrateKbps = prefs.getInt(KEY_MIN_KBPS, DEFAULT_MIN_KBPS),
xorKeyHex = prefs.getString(KEY_XOR_KEY_HEX, "").orEmpty()
)
}
@@ -47,6 +49,7 @@ class AvDemoSettingsStore(context: Context) {
putInt(KEY_FPS, settings.fps)
putInt(KEY_MAX_KBPS, settings.maxBitrateKbps)
putInt(KEY_MIN_KBPS, settings.minBitrateKbps)
putString(KEY_XOR_KEY_HEX, settings.xorKeyHex)
}
}
@@ -62,5 +65,6 @@ class AvDemoSettingsStore(context: Context) {
private const val DEFAULT_FPS = 30
private const val DEFAULT_MAX_KBPS = 2000
private const val DEFAULT_MIN_KBPS = 500
private const val KEY_XOR_KEY_HEX = "xor_key_hex"
}
}

View File

@@ -779,17 +779,20 @@ class LivePlayActivity : AppCompatActivity() {
const val EXTRA_PLAY_STREAM_NAME = "play_stream_name"
const val EXTRA_AUTO_START = "auto_start"
const val EXTRA_PREVIEW_IMAGE_URL = "preview_image_url"
const val EXTRA_XOR_KEY_HEX = "xor_key_hex"
fun createIntent(
context: Context,
liveMode: SellyLiveMode,
streamIdOrUrl: String,
autoStart: Boolean = true
autoStart: Boolean = true,
xorKeyHex: String = ""
): Intent {
return Intent(context, LivePlayActivity::class.java)
.putExtra(EXTRA_PLAY_PROTOCOL, liveMode.name)
.putExtra(EXTRA_STREAM_ID_OR_URL, streamIdOrUrl)
.putExtra(EXTRA_AUTO_START, autoStart)
.putExtra(EXTRA_XOR_KEY_HEX, xorKeyHex)
}
fun createIntentWithParams(
@@ -844,7 +847,8 @@ class LivePlayActivity : AppCompatActivity() {
val streamIdOrUrl: String,
val autoStart: Boolean,
val playParams: PlayParams?,
val previewImageUrl: String?
val previewImageUrl: String?,
val xorKeyHex: String = ""
) {
companion object {
fun from(intent: Intent, env: LiveEnvSettings): Args {
@@ -865,13 +869,15 @@ class LivePlayActivity : AppCompatActivity() {
val input = intent.getStringExtra(EXTRA_STREAM_ID_OR_URL).orEmpty()
.ifBlank { playParams?.streamName ?: env.defaultStreamId }
val autoStart = intent.getBooleanExtra(EXTRA_AUTO_START, true)
val xorKeyHex = intent.getStringExtra(EXTRA_XOR_KEY_HEX).orEmpty().trim()
val mode = resolveLiveMode(rawProtocol, input, env)
return Args(
liveMode = mode,
streamIdOrUrl = input,
autoStart = autoStart,
playParams = playParams,
previewImageUrl = previewImageUrl
previewImageUrl = previewImageUrl,
xorKeyHex = xorKeyHex
)
}
@@ -908,11 +914,12 @@ class LivePlayActivity : AppCompatActivity() {
args.playParams.streamName,
liveMode = args.liveMode,
vhost = args.playParams.vhost,
appName = args.playParams.appName
appName = args.playParams.appName,
xorKeyHex = args.xorKeyHex
)
}
input.contains("://") -> SellyLiveVideoPlayer.initWithUrl(this, input)
else -> SellyLiveVideoPlayer.initWithStreamId(this, input, liveMode = args.liveMode)
input.contains("://") -> SellyLiveVideoPlayer.initWithUrl(this, input, xorKeyHex = args.xorKeyHex)
else -> SellyLiveVideoPlayer.initWithStreamId(this, input, liveMode = args.liveMode, xorKeyHex = args.xorKeyHex)
}
}
}

View File

@@ -144,6 +144,7 @@ class LivePushActivity : AppCompatActivity() {
)
applyStreamConfig(settings)
pusher.token = auth?.tokenResult?.token
pusher.setXorKey(settings.xorKeyHex)
pusher.startLiveWithStreamId(streamId)
}
}
@@ -743,6 +744,7 @@ class LivePushActivity : AppCompatActivity() {
dialogBinding.etFps.setText(current.fps.toString())
dialogBinding.etMaxBitrate.setText(current.maxBitrateKbps.toString())
dialogBinding.etMinBitrate.setText(current.minBitrateKbps.toString())
dialogBinding.etXorKey.setText(current.xorKeyHex)
dialogBinding.etEnvVhost.setText(currentEnv.vhost)
dialogBinding.etEnvVhostKey.setText(currentEnv.vhostKey)
dialogBinding.etEnvAppId.setText(currentEnv.appId)
@@ -789,12 +791,14 @@ class LivePushActivity : AppCompatActivity() {
else -> AvDemoSettings.Resolution.P720
}
val xorKey = dialogBinding.etXorKey.text?.toString()?.trim().orEmpty()
val updated = current.copy(
streamId = streamId,
resolution = res,
fps = fps,
maxBitrateKbps = maxKbps,
minBitrateKbps = minKbps
minBitrateKbps = minKbps,
xorKeyHex = xorKey
)
settingsStore.write(updated)
val envUpdated = currentEnv.copy(

View File

@@ -198,6 +198,30 @@
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="XOR Key (hex)"
android:textColor="@color/av_text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etXorKey"
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="e.g. AABB0102 (留空=不加密)"
android:importantForAutofill="no"
android:inputType="textVisiblePassword"
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"

View File

@@ -276,6 +276,30 @@
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="XOR Key (hex)"
android:textColor="@color/av_text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etXorKey"
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="e.g. AABB0102 (留空=不加密)"
android:importantForAutofill="no"
android:inputType="textVisiblePassword"
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"

View File

@@ -104,6 +104,30 @@
android:textColorHint="@color/av_text_hint"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="XOR Key (hex)"
android:textColor="@color/av_text_primary"
android:textSize="13sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etXorKey"
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="e.g. AABB0102 (留空=不解密)"
android:importantForAutofill="no"
android:inputType="textVisiblePassword"
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/btnStartPlay"
android:layout_width="match_parent"