Finish PS5 on Android

This commit is contained in:
Florian Märkl 2020-12-28 22:30:11 +01:00
commit 5ef9983f95
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
22 changed files with 219 additions and 60 deletions

View file

@ -115,13 +115,13 @@ JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsStopped)(JNIEnv *env, jobject obj
return value == CHIAKI_QUIT_REASON_STOPPED;
}
JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset)
JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec)
{
ChiakiConnectVideoProfile profile = { 0 };
chiaki_connect_video_profile_preset(&profile, (ChiakiVideoResolutionPreset)resolution_preset, (ChiakiVideoFPSPreset)fps_preset);
jclass profile_class = E->FindClass(env, BASE_PACKAGE"/ConnectVideoProfile");
jmethodID profile_ctor = E->GetMethodID(env, profile_class, "<init>", "(IIII)V");
return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate);
jmethodID profile_ctor = E->GetMethodID(env, profile_class, "<init>", "(IIIIL"BASE_PACKAGE"/Codec;)V");
return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate, codec);
}
typedef struct android_chiaki_session_t
@ -198,6 +198,7 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
jclass result_class = E->GetObjectClass(env, result);
jclass connect_info_class = E->GetObjectClass(env, connect_info_obj);
jboolean ps5 = E->GetBooleanField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "ps5", "Z"));
jstring host_string = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "host", "Ljava/lang/String;"));
jbyteArray regist_key_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "registKey", "[B"));
jbyteArray morning_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "morning", "[B"));
@ -205,6 +206,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
jclass connect_video_profile_class = E->GetObjectClass(env, connect_video_profile_obj);
ChiakiConnectInfo connect_info = { 0 };
connect_info.ps5 = ps5;
const char *str_borrow = E->GetStringUTFChars(env, host_string, NULL);
connect_info.host = host_str = strdup(str_borrow);
E->ReleaseStringUTFChars(env, host_string, str_borrow);
@ -239,6 +242,13 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
connect_info.video_profile.max_fps = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "maxFPS", "I"));
connect_info.video_profile.bitrate = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "bitrate", "I"));
jobject codec_obj = E->GetObjectField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "codec", "L"BASE_PACKAGE"/Codec;"));
jclass codec_class = E->GetObjectClass(env, codec_obj);
jint target_value = E->GetIntField(env, codec_obj, E->GetFieldID(env, codec_class, "value", "I"));
connect_info.video_profile.codec = (ChiakiCodec)target_value;
connect_info.video_profile_auto_downgrade = true;
session = CHIAKI_NEW(AndroidChiakiSession);
if(!session)
{
@ -247,7 +257,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject
}
memset(session, 0, sizeof(AndroidChiakiSession));
session->log = log;
err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height);
err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height,
connect_info.ps5 ? connect_info.video_profile.codec : CHIAKI_CODEC_H264);
if(err != CHIAKI_ERR_SUCCESS)
{
free(session);
@ -592,6 +603,8 @@ typedef struct android_chiaki_regist_t
jobject java_regist;
jmethodID java_regist_event_meth;
jclass java_target_class;
jobject java_regist_event_canceled;
jobject java_regist_event_failed;
jclass java_regist_event_success_class;
@ -601,11 +614,10 @@ typedef struct android_chiaki_regist_t
jmethodID java_regist_host_ctor;
} AndroidChiakiRegist;
static jobject create_jni_target(JNIEnv *env, ChiakiTarget target)
static jobject create_jni_target(JNIEnv *env, jclass target_class, ChiakiTarget target)
{
jclass cls = E->FindClass(env, BASE_PACKAGE"/Target");
jmethodID meth = E->GetStaticMethodID(env, cls, "fromValue", "(I)L"BASE_PACKAGE"/Target;");
return E->CallStaticObjectMethod(env, cls, meth, (jint)target);
jmethodID meth = E->GetStaticMethodID(env, target_class, "fromValue", "(I)L"BASE_PACKAGE"/Target;");
return E->CallStaticObjectMethod(env, target_class, meth, (jint)target);
}
static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user)
@ -629,7 +641,7 @@ static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user)
{
ChiakiRegisteredHost *host = event->registered_host;
jobject java_host = E->NewObject(env, regist->java_regist_host_class, regist->java_regist_host_ctor,
create_jni_target(env, host->target),
create_jni_target(env, regist->java_target_class, host->target),
jnistr_from_ascii(env, host->ap_ssid),
jnistr_from_ascii(env, host->ap_bssid),
jnistr_from_ascii(env, host->ap_key),
@ -654,6 +666,7 @@ static void android_chiaki_regist_fini_partial(JNIEnv *env, AndroidChiakiRegist
{
android_chiaki_jni_log_fini(&regist->log, env);
E->DeleteGlobalRef(env, regist->java_regist);
E->DeleteGlobalRef(env, regist->java_target_class);
E->DeleteGlobalRef(env, regist->java_regist_event_canceled);
E->DeleteGlobalRef(env, regist->java_regist_event_failed);
E->DeleteGlobalRef(env, regist->java_regist_event_success_class);
@ -676,6 +689,8 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re
regist->java_regist = E->NewGlobalRef(env, java_regist);
regist->java_regist_event_meth = E->GetMethodID(env, E->GetObjectClass(env, regist->java_regist), "event", "(L"BASE_PACKAGE"/RegistEvent;)V");
regist->java_target_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/Target"));
regist->java_regist_event_canceled = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventCanceled"));
regist->java_regist_event_failed = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventFailed"));
regist->java_regist_event_success_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/RegistEventSuccess"));
@ -688,8 +703,8 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re
"Ljava/lang/String;" // apBssid: String
"Ljava/lang/String;" // apKey: String
"Ljava/lang/String;" // apName: String
"[B" // ps4Mac: ByteArray
"Ljava/lang/String;" // ps4Nickname: String
"[B" // serverMac: ByteArray
"Ljava/lang/String;" // serverNickname: String
"[B" // rpRegistKey: ByteArray
"I" // rpKeyType: UInt
"[B" // rpKey: ByteArray

View file

@ -14,13 +14,15 @@
static void *android_chiaki_video_decoder_output_thread_func(void *user);
ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height)
ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec)
{
decoder->log = log;
decoder->codec = NULL;
decoder->timestamp_cur = 0;
decoder->target_width = target_width;
decoder->target_height = target_height;
decoder->target_codec = codec;
decoder->shutdown_output = false;
return chiaki_mutex_init(&decoder->codec_mutex, false);
}
@ -29,17 +31,20 @@ void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder)
if(decoder->codec)
{
chiaki_mutex_lock(&decoder->codec_mutex);
decoder->shutdown_output = true;
ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, -1);
if(codec_buf_index >= 0)
{
CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer");
AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
chiaki_thread_join(&decoder->output_thread, NULL);
}
else
{
CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!");
AMediaCodec_stop(decoder->codec);
chiaki_mutex_unlock(&decoder->codec_mutex);
}
AMediaCodec_delete(decoder->codec);
@ -67,7 +72,8 @@ void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder
decoder->window = ANativeWindow_fromSurface(env, surface);
const char *mime = "video/avc";
const char *mime = chiaki_codec_is_h265(decoder->target_codec) ? "video/hevc" : "video/avc";
CHIAKI_LOGI(decoder->log, "Initializing decoder with mime %s", mime);
decoder->codec = AMediaCodec_createDecoderByType(mime);
if(!decoder->codec)
@ -181,6 +187,17 @@ static void *android_chiaki_video_decoder_output_thread_func(void *user)
break;
}
}
else
{
chiaki_mutex_lock(&decoder->codec_mutex);
bool shutdown = decoder->shutdown_output;
chiaki_mutex_unlock(&decoder->codec_mutex);
if(shutdown)
{
CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread detected shutdown after reported error");
break;
}
}
}
CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread exiting");

View file

@ -19,11 +19,13 @@ typedef struct android_chiaki_video_decoder_t
ANativeWindow *window;
uint64_t timestamp_cur;
ChiakiThread output_thread;
bool shutdown_output;
int32_t target_width;
int32_t target_height;
ChiakiCodec target_codec;
} AndroidChiakiVideoDecoder;
ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height);
ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec);
void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder);
void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder, JNIEnv *env, jobject surface);
bool android_chiaki_video_decoder_video_sample(uint8_t *buf, size_t buf_size, void *user);

View file

@ -10,6 +10,7 @@ sealed class DisplayHost
abstract val host: String
abstract val name: String?
abstract val id: String?
abstract val isPS5: Boolean
val isRegistered get() = registeredHost != null
}
@ -22,6 +23,7 @@ class DiscoveredDisplayHost(
override val host get() = discoveredHost.hostAddr ?: ""
override val name get() = discoveredHost.hostName ?: registeredHost?.serverNickname
override val id get() = discoveredHost.hostId ?: registeredHost?.serverMac?.toString()
override val isPS5 get() = discoveredHost.isPS5
override fun equals(other: Any?): Boolean =
if(other !is DiscoveredDisplayHost)
@ -42,6 +44,7 @@ class ManualDisplayHost(
override val host get() = manualHost.host
override val name get() = registeredHost?.serverNickname
override val id get() = registeredHost?.serverMac?.toString()
override val isPS5: Boolean get() = registeredHost?.target?.isPS5 ?: false
override fun equals(other: Any?): Boolean =
if(other !is ManualDisplayHost)

View file

@ -7,6 +7,7 @@ import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.preference.PreferenceManager
import com.metallic.chiaki.R
import com.metallic.chiaki.lib.Codec
import com.metallic.chiaki.lib.ConnectVideoProfile
import com.metallic.chiaki.lib.VideoFPSPreset
import com.metallic.chiaki.lib.VideoResolutionPreset
@ -31,12 +32,20 @@ class Preferences(context: Context)
FPS_60("60", R.string.preferences_fps_title_60, VideoFPSPreset.FPS_60)
}
enum class Codec(val value: String, @StringRes val title: Int, val codec: com.metallic.chiaki.lib.Codec)
{
CODEC_H264("h264", R.string.preferences_codec_title_h264, com.metallic.chiaki.lib.Codec.CODEC_H264),
CODEC_H265("h265", R.string.preferences_codec_title_h265, com.metallic.chiaki.lib.Codec.CODEC_H265)
}
companion object
{
val resolutionDefault = Resolution.RES_720P
val resolutionAll = Resolution.values()
val fpsDefault = FPS.FPS_60
val fpsAll = FPS.values()
val codecDefault = Codec.CODEC_H265
val codecAll = Codec.values()
}
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@ -97,12 +106,19 @@ class Preferences(context: Context)
private val bitrateAutoSubject by lazy { BehaviorSubject.createDefault(bitrateAuto) }
val bitrateAutoObservable: Observable<Int> get() = bitrateAutoSubject
private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset)
val codecKey get() = resources.getString(R.string.preferences_codec_key)
var codec
get() = sharedPreferences.getString(codecKey, codecDefault.value)?.let { value ->
Codec.values().firstOrNull { it.value == value }
} ?: codecDefault
set(value) { sharedPreferences.edit().putString(codecKey, value.value).apply() }
private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset, codec.codec)
val videoProfile get() = videoProfileDefaultBitrate.let {
val bitrate = bitrate
if(bitrate == null)
it
else
ConnectVideoProfile(it.width, it.height, it.maxFPS, bitrate)
it.copy(bitrate = bitrate)
}
}

View file

@ -106,7 +106,7 @@ private fun Moshi.serializedSettingsAdapter() =
private const val KEY_FORMAT = "format"
private const val FORMAT = "chiaki-settings"
private const val KEY_VERSION = "version"
private const val VERSION = 1
private const val VERSION = 2
private const val KEY_SETTINGS = "settings"
fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db)

View file

@ -21,7 +21,7 @@ import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.util.concurrent.TimeUnit
val DiscoveryHost.ps4Mac get() = this.hostId?.hexToByteArray()?.let {
val DiscoveryHost.serverMac get() = this.hostId?.hexToByteArray()?.let {
if(it.size == MacAddress.LENGTH)
MacAddress(it)
else

View file

@ -41,23 +41,32 @@ enum class VideoFPSPreset(val value: Int)
FPS_60(60)
}
enum class Codec(val value: Int)
{
CODEC_H264(0),
CODEC_H265(1),
CODEC_H265_HDR(2)
}
@Parcelize
data class ConnectVideoProfile(
val width: Int,
val height: Int,
val maxFPS: Int,
val bitrate: Int
val bitrate: Int,
val codec: Codec
): Parcelable
{
companion object
{
fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset)
= ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value)
fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset, codec: Codec)
= ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value, codec)
}
}
@Parcelize
data class ConnectInfo(
val ps5: Boolean,
val host: String,
val registKey: ByteArray,
val morning: ByteArray,
@ -76,7 +85,7 @@ private class ChiakiNative
@JvmStatic external fun errorCodeToString(value: Int): String
@JvmStatic external fun quitReasonToString(value: Int): String
@JvmStatic external fun quitReasonIsStopped(value: Int): Boolean
@JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int): ConnectVideoProfile
@JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile
@JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session)
@JvmStatic external fun sessionFree(ptr: Long)
@JvmStatic external fun sessionStart(ptr: Long): Int
@ -284,6 +293,8 @@ data class DiscoveryHost(
READY,
STANDBY
}
val isPS5 get() = deviceDiscoveryProtocolVersion == "00030010"
}

View file

@ -75,15 +75,18 @@ class DisplayHostRecyclerViewAdapter(
} ?: ""
it.discoveredIndicatorLayout.visibility = if(host is DiscoveredDisplayHost) View.VISIBLE else View.GONE
it.stateIndicatorImageView.setImageResource(
if(host is DiscoveredDisplayHost)
when(host.discoveredHost.state)
when
{
host is DiscoveredDisplayHost -> when(host.discoveredHost.state)
{
DiscoveryHost.State.STANDBY -> R.drawable.ic_console_standby
DiscoveryHost.State.READY -> R.drawable.ic_console_ready
else -> R.drawable.ic_console
DiscoveryHost.State.STANDBY -> if(host.isPS5) R.drawable.ic_console_ps5_standby else R.drawable.ic_console_standby
DiscoveryHost.State.READY -> if(host.isPS5) R.drawable.ic_console_ps5_ready else R.drawable.ic_console_ready
else -> if(host.isPS5) R.drawable.ic_console_ps5 else R.drawable.ic_console
}
else
R.drawable.ic_console)
host.isPS5 -> R.drawable.ic_console_ps5
else -> R.drawable.ic_console
}
)
it.setOnClickListener { clickCallback(host) }
val canWakeup = host.registeredHost != null

View file

@ -170,7 +170,7 @@ class MainActivity : AppCompatActivity()
if(registeredHost != null)
{
fun connect() {
val connectInfo = ConnectInfo(host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile)
val connectInfo = ConnectInfo(host.isPS5, host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile)
Intent(this, StreamActivity::class.java).let {
it.putExtra(StreamActivity.EXTRA_CONNECT_INFO, connectInfo)
startActivity(it)

View file

@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
import com.metallic.chiaki.common.*
import com.metallic.chiaki.common.ext.toLiveData
import com.metallic.chiaki.discovery.DiscoveryManager
import com.metallic.chiaki.discovery.ps4Mac
import com.metallic.chiaki.discovery.serverMac
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
@ -34,7 +34,7 @@ class MainViewModel(val database: AppDatabase, val preferences: Preferences): Vi
val macRegisteredHosts = registeredHosts.associateBy { it.serverMac }
val idRegisteredHosts = registeredHosts.associateBy { it.id }
discoveredHosts.map {
DiscoveredDisplayHost(it.ps4Mac?.let { mac -> macRegisteredHosts[mac] }, it)
DiscoveredDisplayHost(it.serverMac?.let { mac -> macRegisteredHosts[mac] }, it)
} +
manualHosts.map {
ManualDisplayHost(it.registeredHost?.let { id -> idRegisteredHosts[id] }, it)

View file

@ -49,27 +49,29 @@ class RegistActivity: AppCompatActivity(), RevealActivity
registButton.setOnClickListener { doRegist() }
ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_8) {
RegistViewModel.PS4Version.GE_8 -> R.id.ps4VersionGE8RadioButton
RegistViewModel.PS4Version.GE_7 -> R.id.ps4VersionGE7RadioButton
RegistViewModel.PS4Version.LT_7 -> R.id.ps4VersionLT7RadioButton
ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5) {
RegistViewModel.ConsoleVersion.PS5 -> R.id.ps5RadioButton
RegistViewModel.ConsoleVersion.PS4_GE_8 -> R.id.ps4VersionGE8RadioButton
RegistViewModel.ConsoleVersion.PS4_GE_7 -> R.id.ps4VersionGE7RadioButton
RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.id.ps4VersionLT7RadioButton
})
ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId ->
viewModel.ps4Version.value = when(checkedId)
{
R.id.ps4VersionGE8RadioButton -> RegistViewModel.PS4Version.GE_8
R.id.ps4VersionGE7RadioButton -> RegistViewModel.PS4Version.GE_7
R.id.ps4VersionLT7RadioButton -> RegistViewModel.PS4Version.LT_7
else -> RegistViewModel.PS4Version.GE_7
R.id.ps5RadioButton -> RegistViewModel.ConsoleVersion.PS5
R.id.ps4VersionGE8RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_8
R.id.ps4VersionGE7RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_7
R.id.ps4VersionLT7RadioButton -> RegistViewModel.ConsoleVersion.PS4_LT_7
else -> RegistViewModel.ConsoleVersion.PS5
}
}
viewModel.ps4Version.observe(this, Observer {
psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.PS4Version.LT_7) View.GONE else View.VISIBLE
psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.ConsoleVersion.PS4_LT_7) View.GONE else View.VISIBLE
psnIdTextInputLayout.hint = getString(when(it!!)
{
RegistViewModel.PS4Version.LT_7 -> R.string.hint_regist_psn_online_id
RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.hint_regist_psn_online_id
else -> R.string.hint_regist_psn_account_id
})
})
@ -77,22 +79,22 @@ class RegistActivity: AppCompatActivity(), RevealActivity
private fun doRegist()
{
val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_7
val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5
val host = hostEditText.text.toString().trim()
val hostValid = host.isNotEmpty()
val broadcast = broadcastCheckBox.isChecked
val psnId = psnIdEditText.text.toString().trim()
val psnOnlineId: String? = if(ps4Version == RegistViewModel.PS4Version.LT_7) psnId else null
val psnOnlineId: String? = if(ps4Version == RegistViewModel.ConsoleVersion.PS4_LT_7) psnId else null
val psnAccountId: ByteArray? =
if(ps4Version != RegistViewModel.PS4Version.LT_7)
if(ps4Version != RegistViewModel.ConsoleVersion.PS4_LT_7)
try { Base64.decode(psnId, Base64.DEFAULT) } catch(e: IllegalArgumentException) { null }
else
null
val psnIdValid = when(ps4Version)
{
RegistViewModel.PS4Version.LT_7 -> psnOnlineId?.isNotEmpty() ?: false
RegistViewModel.ConsoleVersion.PS4_LT_7 -> psnOnlineId?.isNotEmpty() ?: false
else -> psnAccountId != null && psnAccountId.size == RegistInfo.ACCOUNT_ID_SIZE
}
@ -105,7 +107,7 @@ class RegistActivity: AppCompatActivity(), RevealActivity
if(!psnIdValid)
getString(when(ps4Version)
{
RegistViewModel.PS4Version.LT_7 -> R.string.regist_psn_online_id_invalid
RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.regist_psn_online_id_invalid
else -> R.string.regist_psn_account_id_invalid
})
else
@ -117,9 +119,10 @@ class RegistActivity: AppCompatActivity(), RevealActivity
val target = when(ps4Version)
{
RegistViewModel.PS4Version.GE_8 -> Target.PS4_10
RegistViewModel.PS4Version.GE_7 -> Target.PS4_9
RegistViewModel.PS4Version.LT_7 -> Target.PS4_8
RegistViewModel.ConsoleVersion.PS5 -> Target.PS5_1
RegistViewModel.ConsoleVersion.PS4_GE_8 -> Target.PS4_10
RegistViewModel.ConsoleVersion.PS4_GE_7 -> Target.PS4_9
RegistViewModel.ConsoleVersion.PS4_LT_7 -> Target.PS4_8
}
val registInfo = RegistInfo(target, host, broadcast, psnOnlineId, psnAccountId, pin.toInt())

View file

@ -7,11 +7,12 @@ import androidx.lifecycle.ViewModel
class RegistViewModel: ViewModel()
{
enum class PS4Version {
GE_8,
GE_7,
LT_7
enum class ConsoleVersion {
PS5,
PS4_GE_8,
PS4_GE_7,
PS4_LT_7
}
val ps4Version = MutableLiveData<PS4Version>(PS4Version.GE_8)
val ps4Version = MutableLiveData<ConsoleVersion>(ConsoleVersion.PS5)
}

View file

@ -16,6 +16,7 @@ import com.metallic.chiaki.common.exportAndShareAllSettings
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.common.importSettingsFromUri
import com.metallic.chiaki.lib.Codec
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
@ -42,6 +43,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
preferences.resolutionKey -> preferences.resolution.value
preferences.fpsKey -> preferences.fps.value
preferences.bitrateKey -> preferences.bitrate?.toString() ?: ""
preferences.codecKey -> preferences.codec.value
else -> defValue
}
@ -60,6 +62,11 @@ class DataStore(val preferences: Preferences): PreferenceDataStore()
preferences.fps = fps
}
preferences.bitrateKey -> preferences.bitrate = value?.toIntOrNull()
preferences.codecKey ->
{
val codec = Preferences.Codec.values().firstOrNull { it.value == value } ?: return
preferences.codec = codec
}
}
}
}
@ -111,6 +118,11 @@ class SettingsFragment: PreferenceFragmentCompat(), TitleFragment
bitratePreference?.summaryProvider = bitrateSummaryProvider
})
preferenceScreen.findPreference<ListPreference>(getString(R.string.preferences_codec_key))?.let {
it.entryValues = Preferences.codecAll.map { codec -> codec.value }.toTypedArray()
it.entries = Preferences.codecAll.map { codec -> getString(codec.title) }.toTypedArray()
}
val registeredHostsPreference = preferenceScreen.findPreference<Preference>("registered_hosts")
viewModel.registeredHostsCount.observe(this, Observer {
registeredHostsPreference?.summary = getString(R.string.preferences_registered_hosts_summary, it)

View file

@ -0,0 +1,15 @@
<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="?android:attr/textColorPrimary"
android:pathData="M3,6h18v5h2V6c0,-1.1 -0.9,-2 -2,-2H3C1.9,4 1,4.9 1,6v12c0,1.1 0.9,2 2,2h9v-2H3V6z"/>
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M15,12l-6,-4l0,8z"/>
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M22.71,18.43c0.03,-0.29 0.04,-0.58 0.01,-0.86l1.07,-0.85c0.1,-0.08 0.12,-0.21 0.06,-0.32l-1.03,-1.79c-0.06,-0.11 -0.19,-0.15 -0.31,-0.11L21.23,15c-0.23,-0.17 -0.48,-0.31 -0.75,-0.42l-0.2,-1.36C20.26,13.09 20.16,13 20.03,13h-2.07c-0.12,0 -0.23,0.09 -0.25,0.21l-0.2,1.36c-0.26,0.11 -0.51,0.26 -0.74,0.42l-1.28,-0.5c-0.12,-0.05 -0.25,0 -0.31,0.11l-1.03,1.79c-0.06,0.11 -0.04,0.24 0.06,0.32l1.07,0.86c-0.03,0.29 -0.04,0.58 -0.01,0.86l-1.07,0.85c-0.1,0.08 -0.12,0.21 -0.06,0.32l1.03,1.79c0.06,0.11 0.19,0.15 0.31,0.11l1.27,-0.5c0.23,0.17 0.48,0.31 0.75,0.42l0.2,1.36c0.02,0.12 0.12,0.21 0.25,0.21h2.07c0.12,0 0.23,-0.09 0.25,-0.21l0.2,-1.36c0.26,-0.11 0.51,-0.26 0.74,-0.42l1.28,0.5c0.12,0.05 0.25,0 0.31,-0.11l1.03,-1.79c0.06,-0.11 0.04,-0.24 -0.06,-0.32L22.71,18.43zM19,19.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S19.83,19.5 19,19.5z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="136dp"
android:viewportWidth="135.46666"
android:viewportHeight="35.983334">
<path
android:pathData="M135.059,0.004C105.38,2.679 69.761,6.719 7.637,2.238 2.368,1.858 -2.01,10.53 0.965,10.491 65.38,9.662 117.287,1.775 124.96,2.935c1.336,0.202 1.574,0.634 2.277,3.271 0.014,0.056 0.023,0.078 0.037,0.13C127.222,6.335 127.18,6.334 127.127,6.333 111.134,6.269 42.596,12.28 1.908,11.764V19.744C42.727,19.226 111.141,25.276 127.21,25.172c-0.021,0.076 -0.034,0.111 -0.056,0.195 -0.703,2.637 -0.941,3.069 -2.278,3.271C117.203,29.799 65.81,22.017 1.395,21.187 0.89,21.181 0.027,21.658 0.07,22.395 0.3,26.365 0.437,35.819 1.119,35.819c9.352,0.013 30.52,0.601 40.773,-0.532 18.706,-2.068 32.929,-4.728 44.502,-5.39 21.532,-1.232 33.907,0.053 48.583,1.672 0.933,0.103 0.031,-1.246 -0.448,-1.241 -1.987,0.023 -4.222,-2.731 -6.798,-4.992 -0.019,-0.016 -0.028,-0.082 -0.032,-0.174 1.321,-0.022 2.268,-0.087 2.695,-0.217V16.046,15.462 6.562c-0.424,-0.129 -1.327,-0.197 -2.607,-0.219 0.006,-0.053 0.013,-0.096 0.027,-0.107 2.576,-2.261 4.811,-5.015 6.797,-4.992 0.479,0.005 1.384,-1.325 0.448,-1.241z"
android:fillColor="?android:attr/textColorPrimary"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="136dp"
android:viewportWidth="135.46666"
android:viewportHeight="35.983334">
<path
android:pathData="M1.909,11.763V8.79L127.635,1.807V29.541L1.909,22.902v-3.158l6.691,-3.645z"
android:strokeLineJoin="miter"
android:strokeWidth="0.264483"
android:fillColor="#00a7ff"
android:strokeColor="#00000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M135.059,0.004C105.38,2.679 69.761,6.719 7.637,2.238 2.368,1.858 -2.01,10.53 0.965,10.491 65.38,9.662 117.287,1.775 124.96,2.935c1.336,0.202 1.574,0.634 2.277,3.271 0.014,0.056 0.023,0.078 0.037,0.13C127.222,6.335 127.18,6.334 127.127,6.333 111.134,6.269 42.596,12.28 1.908,11.764V19.744C42.727,19.226 111.141,25.276 127.21,25.172c-0.021,0.076 -0.034,0.111 -0.056,0.195 -0.703,2.637 -0.941,3.069 -2.278,3.271C117.203,29.799 65.81,22.017 1.395,21.187 0.89,21.181 0.027,21.658 0.07,22.395 0.3,26.365 0.437,35.819 1.119,35.819c9.352,0.013 30.52,0.601 40.773,-0.532 18.706,-2.068 32.929,-4.728 44.502,-5.39 21.532,-1.232 33.907,0.053 48.583,1.672 0.933,0.103 0.031,-1.246 -0.448,-1.241 -1.987,0.023 -4.222,-2.731 -6.798,-4.992 -0.019,-0.016 -0.028,-0.082 -0.032,-0.174 1.321,-0.022 2.268,-0.087 2.695,-0.217V16.046,15.462 6.562c-0.424,-0.129 -1.327,-0.197 -2.607,-0.219 0.006,-0.053 0.013,-0.096 0.027,-0.107 2.576,-2.261 4.811,-5.015 6.797,-4.992 0.479,0.005 1.384,-1.325 0.448,-1.241z"
android:fillColor="?android:attr/textColorPrimary"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="136dp"
android:viewportWidth="135.46666"
android:viewportHeight="35.983334">
<path
android:pathData="M1.909,11.763V8.79L127.635,1.807V29.541L1.909,22.902v-3.158l6.691,-3.645z"
android:strokeLineJoin="miter"
android:strokeWidth="0.264483"
android:fillColor="#ffae2f"
android:strokeColor="#00000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M135.059,0.004C105.38,2.679 69.761,6.719 7.637,2.238 2.368,1.858 -2.01,10.53 0.965,10.491 65.38,9.662 117.287,1.775 124.96,2.935c1.336,0.202 1.574,0.634 2.277,3.271 0.014,0.056 0.023,0.078 0.037,0.13C127.222,6.335 127.18,6.334 127.127,6.333 111.134,6.269 42.596,12.28 1.908,11.764V19.744C42.727,19.226 111.141,25.276 127.21,25.172c-0.021,0.076 -0.034,0.111 -0.056,0.195 -0.703,2.637 -0.941,3.069 -2.278,3.271C117.203,29.799 65.81,22.017 1.395,21.187 0.89,21.181 0.027,21.658 0.07,22.395 0.3,26.365 0.437,35.819 1.119,35.819c9.352,0.013 30.52,0.601 40.773,-0.532 18.706,-2.068 32.929,-4.728 44.502,-5.39 21.532,-1.232 33.907,0.053 48.583,1.672 0.933,0.103 0.031,-1.246 -0.448,-1.241 -1.987,0.023 -4.222,-2.731 -6.798,-4.992 -0.019,-0.016 -0.028,-0.082 -0.032,-0.174 1.321,-0.022 2.268,-0.087 2.695,-0.217V16.046,15.462 6.562c-0.424,-0.129 -1.327,-0.197 -2.607,-0.219 0.006,-0.053 0.013,-0.096 0.027,-0.107 2.576,-2.261 4.811,-5.015 6.797,-4.992 0.479,0.005 1.384,-1.325 0.448,-1.241z"
android:fillColor="?android:attr/textColorPrimary"/>
</vector>

View file

@ -66,9 +66,17 @@
android:id="@+id/ps4VersionRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/broadcastCheckBox">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/ps5RadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/regist_option_ps5"
android:layout_weight="1"
android:checked="true"/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/ps4VersionGE8RadioButton"
android:layout_width="wrap_content"
@ -90,7 +98,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/regist_option_ps4_lt_7"
android:layout_weight="1"/>
android:layout_weight="1"/>
</RadioGroup>
<com.google.android.material.textview.MaterialTextView

View file

@ -88,11 +88,13 @@
android:id="@+id/stateIndicatorImageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/ic_console"
android:src="@drawable/ic_console_ps5"
app:layout_constraintTop_toBottomOf="@id/idTextView"
app:layout_constraintBottom_toTopOf="@id/bottomInfoTextView"
android:layout_marginLeft="32dp"
android:layout_marginRight="32dp"
android:layout_marginTop="8dp"/>
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/bottomInfoTextView"
@ -103,7 +105,6 @@
android:fontFamily="sans-serif-condensed-light"
android:gravity="center"
tools:text="App: Persona 5\nTitle ID: 1337"
app:layout_constraintTop_toBottomOf="@id/stateIndicatorImageView"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
android:maxLines="2"

View file

@ -30,6 +30,7 @@
<string name="regist_option_ps4_lt_7">PS4 &lt; 7.0</string>
<string name="regist_option_ps4_ge_7">PS4 ≥ 7.0, &lt; 8</string>
<string name="regist_option_ps4_ge_8">PS4 ≥ 8.0</string>
<string name="regist_option_ps5">PS5</string>
<string name="regist_psn_account_id_help">About obtaining your Account ID, see</string>
<string name="regist_psn_account_id_help_url">https://git.sr.ht/~thestr4ng3r/chiaki/tree/master/item/README.md#obtaining-your-psn-accountid</string>
<string name="hint_regist_psn_online_id">PSN Online ID (username, case-sensitive)</string>
@ -71,6 +72,7 @@
<string name="preferences_resolution_title">Resolution</string>
<string name="preferences_fps_title">FPS</string>
<string name="preferences_bitrate_title">Bitrate</string>
<string name="preferences_codec_title">Codec</string>
<string name="preferences_log_verbose_title">Verbose Logging</string>
<string name="preferences_log_verbose_summary">Warning: This logs a LOT! Don\'t enable for regular use.</string>
<string name="preferences_resolution_title_360p">360p</string>
@ -80,6 +82,8 @@
<string name="preferences_fps_title_30">30</string>
<string name="preferences_fps_title_60">60</string>
<string name="preferences_bitrate_auto">Auto (%d)</string>
<string name="preferences_codec_title_h264">H264</string>
<string name="preferences_codec_title_h265">H265 (PS5 only)</string>
<string name="preferences_swap_cross_moon_title">Swap Cross/Moon and Box/Pyramid Buttons</string>
<string name="preferences_swap_cross_moon_summary">Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers)</string>
<string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string>
@ -103,4 +107,5 @@
<string name="preferences_resolution_key">stream_resolution</string>
<string name="preferences_fps_key">stream_fps</string>
<string name="preferences_bitrate_key">stream_bitrate</string>
<string name="preferences_codec_key">stream_codec</string>
</resources>

View file

@ -52,6 +52,12 @@
app:key="@string/preferences_bitrate_key"
app:title="@string/preferences_bitrate_title"
app:icon="@drawable/ic_bitrate"/>
<ListPreference
app:key="@string/preferences_codec_key"
app:title="@string/preferences_codec_title"
app:summary="%s"
app:icon="@drawable/ic_codec"/>
</PreferenceCategory>
<PreferenceCategory