diff --git a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt deleted file mode 100644 index 4bf7da8..0000000 --- a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt +++ /dev/null @@ -1,228 +0,0 @@ -/* - * This file is part of Chiaki. - * - * Chiaki is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Chiaki is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Chiaki. If not, see . - */ - -package com.metallic.chiaki - -import android.graphics.SurfaceTexture -import android.util.Log -import android.view.* -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.metallic.chiaki.lib.* - -sealed class StreamState -object StreamStateIdle: StreamState() -object StreamStateConnecting: StreamState() -object StreamStateConnected: StreamState() -data class StreamStateCreateError(val error: CreateError): StreamState() -data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() -data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() - -class StreamSession(val connectInfo: ConnectInfo) -{ - var session: Session? = null - private set - - private val _state = MutableLiveData(StreamStateIdle) - val state: LiveData get() = _state - - private val keyControllerState = ControllerState() // from KeyEvents - private val motionControllerState = ControllerState() // from MotionEvents - private var touchControllerState = ControllerState() - - var surfaceTexture: SurfaceTexture? = null - - fun shutdown() - { - session?.stop() - session?.dispose() - session = null - _state.value = StreamStateIdle - //surfaceTexture?.release() - } - - fun pause() - { - shutdown() - } - - fun resume() - { - if(session != null) - return - try - { - val session = Session(connectInfo) - _state.value = StreamStateConnecting - session.eventCallback = this::eventCallback - session.start() - val surfaceTexture = surfaceTexture - if(surfaceTexture != null) - session.setSurface(Surface(surfaceTexture)) - this.session = session - } - catch(e: CreateError) - { - _state.value = StreamStateCreateError(e) - } - } - - private fun eventCallback(event: Event) - { - when(event) - { - is ConnectedEvent -> _state.postValue(StreamStateConnected) - is QuitEvent -> _state.postValue(StreamStateQuit(event.reason, event.reasonString)) - is LoginPinRequestEvent -> _state.postValue(StreamStateLoginPinRequest(event.pinIncorrect)) - } - } - - fun attachToTextureView(textureView: TextureView) - { - textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener { - override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) - { - if(surfaceTexture != null) - return - surfaceTexture = surface - session?.setSurface(Surface(surface)) - } - - override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean - { - // return false if we want to keep the surface texture - return surfaceTexture == null - } - - override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { } - override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {} - } - - val surfaceTexture = surfaceTexture - if(surfaceTexture != null) - textureView.surfaceTexture = surfaceTexture - } - - fun setLoginPin(pin: String) - { - session?.setLoginPin(pin) - } - - fun dispatchKeyEvent(event: KeyEvent): Boolean - { - Log.i("StreamSession", "key event $event") - if(event.action != KeyEvent.ACTION_DOWN && event.action != KeyEvent.ACTION_UP) - return false - - when(event.keyCode) - { - KeyEvent.KEYCODE_BUTTON_L2 -> { - keyControllerState.l2State = if(event.action == KeyEvent.ACTION_DOWN) UByte.MAX_VALUE else 0U - return true - } - KeyEvent.KEYCODE_BUTTON_R2 -> { - keyControllerState.r2State = if(event.action == KeyEvent.ACTION_DOWN) UByte.MAX_VALUE else 0U - return true - } - } - - val buttonMask: UInt = when(event.keyCode) - { - // dpad handled by MotionEvents - //KeyEvent.KEYCODE_DPAD_LEFT -> ControllerState.BUTTON_DPAD_LEFT - //KeyEvent.KEYCODE_DPAD_RIGHT -> ControllerState.BUTTON_DPAD_RIGHT - //KeyEvent.KEYCODE_DPAD_UP -> ControllerState.BUTTON_DPAD_UP - //KeyEvent.KEYCODE_DPAD_DOWN -> ControllerState.BUTTON_DPAD_DOWN - KeyEvent.KEYCODE_BUTTON_A -> ControllerState.BUTTON_CROSS - KeyEvent.KEYCODE_BUTTON_B -> ControllerState.BUTTON_MOON - KeyEvent.KEYCODE_BUTTON_X -> ControllerState.BUTTON_BOX - KeyEvent.KEYCODE_BUTTON_Y -> ControllerState.BUTTON_PYRAMID - KeyEvent.KEYCODE_BUTTON_L1 -> ControllerState.BUTTON_L1 - KeyEvent.KEYCODE_BUTTON_R1 -> ControllerState.BUTTON_R1 - KeyEvent.KEYCODE_BUTTON_THUMBL -> ControllerState.BUTTON_L3 - KeyEvent.KEYCODE_BUTTON_THUMBR -> ControllerState.BUTTON_R3 - KeyEvent.KEYCODE_BUTTON_SELECT -> ControllerState.BUTTON_SHARE - KeyEvent.KEYCODE_BUTTON_START -> ControllerState.BUTTON_OPTIONS - KeyEvent.KEYCODE_BUTTON_C -> ControllerState.BUTTON_PS - KeyEvent.KEYCODE_BUTTON_MODE -> ControllerState.BUTTON_PS - else -> return false - } - - keyControllerState.buttons = keyControllerState.buttons.run { - when(event.action) - { - KeyEvent.ACTION_DOWN -> this or buttonMask - KeyEvent.ACTION_UP -> this and buttonMask.inv() - else -> this - } - } - - sendControllerState() - return true - } - - fun onGenericMotionEvent(event: MotionEvent): Boolean - { - if(event.source and InputDevice.SOURCE_CLASS_JOYSTICK != InputDevice.SOURCE_CLASS_JOYSTICK) - return false - fun Float.signedAxis() = (this * Short.MAX_VALUE).toShort() - fun Float.unsignedAxis() = (this * UByte.MAX_VALUE.toFloat()).toUInt().toUByte() - motionControllerState.leftX = event.getAxisValue(MotionEvent.AXIS_X).signedAxis() - motionControllerState.leftY = event.getAxisValue(MotionEvent.AXIS_Y).signedAxis() - motionControllerState.rightX = event.getAxisValue(MotionEvent.AXIS_Z).signedAxis() - motionControllerState.rightY = event.getAxisValue(MotionEvent.AXIS_RZ).signedAxis() - motionControllerState.l2State = event.getAxisValue(MotionEvent.AXIS_LTRIGGER).unsignedAxis() - motionControllerState.r2State = event.getAxisValue(MotionEvent.AXIS_RTRIGGER).unsignedAxis() - motionControllerState.buttons = motionControllerState.buttons.let { - val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X) - val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y) - val dpadButtons = - (if(dpadX > 0.5f) ControllerState.BUTTON_DPAD_RIGHT else 0U) or - (if(dpadX < -0.5f) ControllerState.BUTTON_DPAD_LEFT else 0U) or - (if(dpadY > 0.5f) ControllerState.BUTTON_DPAD_DOWN else 0U) or - (if(dpadY < -0.5f) ControllerState.BUTTON_DPAD_UP else 0U) - it and (ControllerState.BUTTON_DPAD_RIGHT or - ControllerState.BUTTON_DPAD_LEFT or - ControllerState.BUTTON_DPAD_DOWN or - ControllerState.BUTTON_DPAD_UP).inv() or - dpadButtons - } - Log.i("StreamSession", "motionEvent => $motionControllerState") - sendControllerState() - return true - } - - fun updateTouchControllerState(controllerState: ControllerState) - { - touchControllerState = controllerState - sendControllerState() - } - - private fun sendControllerState() - { - val controllerState = keyControllerState or motionControllerState - - // prioritize motion controller's l2 and r2 over key - // (some controllers send only key, others both but key earlier than full press) - if(motionControllerState.l2State > 0U) - controllerState.l2State = motionControllerState.l2State - if(motionControllerState.r2State > 0U) - controllerState.r2State = motionControllerState.r2State - - session?.setControllerState(controllerState or touchControllerState) - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt index cd30456..17d8da4 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt @@ -79,6 +79,11 @@ class Preferences(context: Context) get() = sharedPreferences.getBoolean(logVerboseKey, false) set(value) { sharedPreferences.edit().putBoolean(logVerboseKey, value).apply() } + val swapCrossMoonKey get() = resources.getString(R.string.preferences_swap_cross_moon_key) + var swapCrossMoon + get() = sharedPreferences.getBoolean(swapCrossMoonKey, false) + set(value) { sharedPreferences.edit().putBoolean(swapCrossMoonKey, value).apply() } + val resolutionKey get() = resources.getString(R.string.preferences_resolution_key) var resolution get() = sharedPreferences.getString(resolutionKey, resolutionDefault.value)?.let { value -> diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt new file mode 100644 index 0000000..9cd2067 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamInput.kt @@ -0,0 +1,127 @@ +package com.metallic.chiaki.session + +import android.util.Log +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent +import com.metallic.chiaki.common.Preferences +import com.metallic.chiaki.lib.ControllerState + +class StreamInput(val preferences: Preferences) +{ + var controllerStateChangedCallback: ((ControllerState) -> Unit)? = null + + val controllerState: ControllerState get() + { + val controllerState = keyControllerState or motionControllerState + + // prioritize motion controller's l2 and r2 over key + // (some controllers send only key, others both but key earlier than full press) + if(motionControllerState.l2State > 0U) + controllerState.l2State = motionControllerState.l2State + if(motionControllerState.r2State > 0U) + controllerState.r2State = motionControllerState.r2State + + return controllerState or touchControllerState + } + + private val keyControllerState = ControllerState() // from KeyEvents + private val motionControllerState = ControllerState() // from MotionEvents + var touchControllerState = ControllerState() + set(value) + { + field = value + controllerStateUpdated() + } + + private val swapCrossMoon = preferences.swapCrossMoon + + private fun controllerStateUpdated() + { + controllerStateChangedCallback?.let { it(controllerState) } + } + + fun dispatchKeyEvent(event: KeyEvent): Boolean + { + Log.i("StreamSession", "key event $event") + if(event.action != KeyEvent.ACTION_DOWN && event.action != KeyEvent.ACTION_UP) + return false + + when(event.keyCode) + { + KeyEvent.KEYCODE_BUTTON_L2 -> { + keyControllerState.l2State = if(event.action == KeyEvent.ACTION_DOWN) UByte.MAX_VALUE else 0U + return true + } + KeyEvent.KEYCODE_BUTTON_R2 -> { + keyControllerState.r2State = if(event.action == KeyEvent.ACTION_DOWN) UByte.MAX_VALUE else 0U + return true + } + } + + val buttonMask: UInt = when(event.keyCode) + { + // dpad handled by MotionEvents + //KeyEvent.KEYCODE_DPAD_LEFT -> ControllerState.BUTTON_DPAD_LEFT + //KeyEvent.KEYCODE_DPAD_RIGHT -> ControllerState.BUTTON_DPAD_RIGHT + //KeyEvent.KEYCODE_DPAD_UP -> ControllerState.BUTTON_DPAD_UP + //KeyEvent.KEYCODE_DPAD_DOWN -> ControllerState.BUTTON_DPAD_DOWN + KeyEvent.KEYCODE_BUTTON_A -> if(swapCrossMoon) ControllerState.BUTTON_MOON else ControllerState.BUTTON_CROSS + KeyEvent.KEYCODE_BUTTON_B -> if(swapCrossMoon) ControllerState.BUTTON_CROSS else ControllerState.BUTTON_MOON + KeyEvent.KEYCODE_BUTTON_X -> if(swapCrossMoon) ControllerState.BUTTON_PYRAMID else ControllerState.BUTTON_BOX + KeyEvent.KEYCODE_BUTTON_Y -> if(swapCrossMoon) ControllerState.BUTTON_BOX else ControllerState.BUTTON_PYRAMID + KeyEvent.KEYCODE_BUTTON_L1 -> ControllerState.BUTTON_L1 + KeyEvent.KEYCODE_BUTTON_R1 -> ControllerState.BUTTON_R1 + KeyEvent.KEYCODE_BUTTON_THUMBL -> ControllerState.BUTTON_L3 + KeyEvent.KEYCODE_BUTTON_THUMBR -> ControllerState.BUTTON_R3 + KeyEvent.KEYCODE_BUTTON_SELECT -> ControllerState.BUTTON_SHARE + KeyEvent.KEYCODE_BUTTON_START -> ControllerState.BUTTON_OPTIONS + KeyEvent.KEYCODE_BUTTON_C -> ControllerState.BUTTON_PS + KeyEvent.KEYCODE_BUTTON_MODE -> ControllerState.BUTTON_PS + else -> return false + } + + keyControllerState.buttons = keyControllerState.buttons.run { + when(event.action) + { + KeyEvent.ACTION_DOWN -> this or buttonMask + KeyEvent.ACTION_UP -> this and buttonMask.inv() + else -> this + } + } + + controllerStateUpdated() + return true + } + + fun onGenericMotionEvent(event: MotionEvent): Boolean + { + if(event.source and InputDevice.SOURCE_CLASS_JOYSTICK != InputDevice.SOURCE_CLASS_JOYSTICK) + return false + fun Float.signedAxis() = (this * Short.MAX_VALUE).toShort() + fun Float.unsignedAxis() = (this * UByte.MAX_VALUE.toFloat()).toUInt().toUByte() + motionControllerState.leftX = event.getAxisValue(MotionEvent.AXIS_X).signedAxis() + motionControllerState.leftY = event.getAxisValue(MotionEvent.AXIS_Y).signedAxis() + motionControllerState.rightX = event.getAxisValue(MotionEvent.AXIS_Z).signedAxis() + motionControllerState.rightY = event.getAxisValue(MotionEvent.AXIS_RZ).signedAxis() + motionControllerState.l2State = event.getAxisValue(MotionEvent.AXIS_LTRIGGER).unsignedAxis() + motionControllerState.r2State = event.getAxisValue(MotionEvent.AXIS_RTRIGGER).unsignedAxis() + motionControllerState.buttons = motionControllerState.buttons.let { + val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X) + val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y) + val dpadButtons = + (if(dpadX > 0.5f) ControllerState.BUTTON_DPAD_RIGHT else 0U) or + (if(dpadX < -0.5f) ControllerState.BUTTON_DPAD_LEFT else 0U) or + (if(dpadY > 0.5f) ControllerState.BUTTON_DPAD_DOWN else 0U) or + (if(dpadY < -0.5f) ControllerState.BUTTON_DPAD_UP else 0U) + it and (ControllerState.BUTTON_DPAD_RIGHT or + ControllerState.BUTTON_DPAD_LEFT or + ControllerState.BUTTON_DPAD_DOWN or + ControllerState.BUTTON_DPAD_UP).inv() or + dpadButtons + } + //Log.i("StreamSession", "motionEvent => $motionControllerState") + controllerStateUpdated() + return true + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt new file mode 100644 index 0000000..164fafd --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/session/StreamSession.kt @@ -0,0 +1,136 @@ +/* + * This file is part of Chiaki. + * + * Chiaki is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chiaki is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chiaki. If not, see . + */ + +package com.metallic.chiaki.session + +import android.graphics.SurfaceTexture +import android.util.Log +import android.view.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.metallic.chiaki.lib.* + +sealed class StreamState +object StreamStateIdle: StreamState() +object StreamStateConnecting: StreamState() +object StreamStateConnected: StreamState() +data class StreamStateCreateError(val error: CreateError): StreamState() +data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() +data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() + +class StreamSession(val connectInfo: ConnectInfo, val input: StreamInput) +{ + var session: Session? = null + private set + + private val _state = MutableLiveData(StreamStateIdle) + val state: LiveData get() = _state + + var surfaceTexture: SurfaceTexture? = null + + init + { + input.controllerStateChangedCallback = { + session?.setControllerState(it) + } + } + + fun shutdown() + { + session?.stop() + session?.dispose() + session = null + _state.value = StreamStateIdle + //surfaceTexture?.release() + } + + fun pause() + { + shutdown() + } + + fun resume() + { + if(session != null) + return + try + { + val session = Session(connectInfo) + _state.value = StreamStateConnecting + session.eventCallback = this::eventCallback + session.start() + val surfaceTexture = surfaceTexture + if(surfaceTexture != null) + session.setSurface(Surface(surfaceTexture)) + this.session = session + } + catch(e: CreateError) + { + _state.value = StreamStateCreateError(e) + } + } + + private fun eventCallback(event: Event) + { + when(event) + { + is ConnectedEvent -> _state.postValue(StreamStateConnected) + is QuitEvent -> _state.postValue( + StreamStateQuit( + event.reason, + event.reasonString + ) + ) + is LoginPinRequestEvent -> _state.postValue( + StreamStateLoginPinRequest( + event.pinIncorrect + ) + ) + } + } + + fun attachToTextureView(textureView: TextureView) + { + textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) + { + if(surfaceTexture != null) + return + surfaceTexture = surface + session?.setSurface(Surface(surface)) + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean + { + // return false if we want to keep the surface texture + return surfaceTexture == null + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { } + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {} + } + + val surfaceTexture = surfaceTexture + if(surfaceTexture != null) + textureView.surfaceTexture = surfaceTexture + } + + fun setLoginPin(pin: String) + { + session?.setLoginPin(pin) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt index 2ddd837..8ba742b 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamActivity.kt @@ -20,14 +20,9 @@ package com.metallic.chiaki.stream import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.app.AlertDialog -import android.app.Dialog -import android.content.DialogInterface import android.graphics.Matrix import android.os.Bundle import android.os.Handler -import android.speech.tts.TextToSpeech -import android.text.method.Touch -import android.util.Log import android.view.KeyEvent import android.view.MotionEvent import android.view.View @@ -38,13 +33,11 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.* import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.metallic.chiaki.* import com.metallic.chiaki.R import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.lib.ConnectInfo -import com.metallic.chiaki.lib.LoginPinRequestEvent -import com.metallic.chiaki.lib.QuitReason +import com.metallic.chiaki.session.* import com.metallic.chiaki.touchcontrols.TouchControlsFragment import kotlinx.android.synthetic.main.activity_stream.* @@ -68,18 +61,17 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe { super.onCreate(savedInstanceState) - viewModel = ViewModelProviders.of(this, viewModelFactory { StreamViewModel(Preferences(this)) })[StreamViewModel::class.java] - if(!viewModel.isInitialized) + val connectInfo = intent.getParcelableExtra(EXTRA_CONNECT_INFO) + if(connectInfo == null) { - val connectInfo = intent.getParcelableExtra(EXTRA_CONNECT_INFO) - if(connectInfo == null) - { - finish() - return - } - viewModel.init(connectInfo) + finish() + return } + viewModel = ViewModelProviders.of(this, viewModelFactory { + StreamViewModel(Preferences(this), connectInfo) + })[StreamViewModel::class.java] + setContentView(R.layout.activity_stream) window.decorView.setOnSystemUiVisibilityChangeListener(this) @@ -104,7 +96,7 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe super.onAttachFragment(fragment) if(fragment is TouchControlsFragment) { - fragment.controllerStateCallback = viewModel.session::updateTouchControllerState + fragment.controllerStateCallback = { viewModel.input.touchControllerState = it } fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled } } @@ -309,6 +301,6 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe } } - override fun dispatchKeyEvent(event: KeyEvent) = viewModel.session.dispatchKeyEvent(event) || super.dispatchKeyEvent(event) - override fun onGenericMotionEvent(event: MotionEvent) = viewModel.session.onGenericMotionEvent(event) || super.onGenericMotionEvent(event) + override fun dispatchKeyEvent(event: KeyEvent) = viewModel.input.dispatchKeyEvent(event) || super.dispatchKeyEvent(event) + override fun onGenericMotionEvent(event: MotionEvent) = viewModel.input.onGenericMotionEvent(event) || super.onGenericMotionEvent(event) } diff --git a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt index 205f31a..64bdfdc 100644 --- a/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/stream/StreamViewModel.kt @@ -20,27 +20,20 @@ package com.metallic.chiaki.stream import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.metallic.chiaki.StreamSession +import com.metallic.chiaki.session.StreamSession import com.metallic.chiaki.common.Preferences import com.metallic.chiaki.lib.* +import com.metallic.chiaki.session.StreamInput -class StreamViewModel(val preferences: Preferences): ViewModel() +class StreamViewModel(val preferences: Preferences, val connectInfo: ConnectInfo): ViewModel() { - private var connectInfo: ConnectInfo? = null private var _session: StreamSession? = null - val session: StreamSession get() = _session ?: throw UninitializedPropertyAccessException("StreamViewModel not initialized") - val isInitialized get() = connectInfo != null + val input = StreamInput(preferences) + val session = StreamSession(connectInfo, input) private var _onScreenControlsEnabled = MutableLiveData(preferences.onScreenControlsEnabled) val onScreenControlsEnabled: LiveData get() = _onScreenControlsEnabled - fun init(connectInfo: ConnectInfo) - { - if(isInitialized) - return - _session = StreamSession(connectInfo) - } - override fun onCleared() { super.onCleared() diff --git a/android/app/src/main/res/drawable/ic_gamepad.xml b/android/app/src/main/res/drawable/ic_gamepad.xml new file mode 100644 index 0000000..00918bf --- /dev/null +++ b/android/app/src/main/res/drawable/ic_gamepad.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 8d8c1f9..1a5b50c 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -58,6 +58,15 @@ Bitrate Verbose Logging Warning: This logs a LOT! Don\'t enable for regular use. + 360p + 540p + 720p + 1080p + 30 + 60 + Auto (%d) + Swap Cross/Moon and Box/Pyramid Buttons + Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers) Are you sure you want to delete the registered console %s with ID %s? Are you sure you want to delete the console entry for %s? Keep @@ -68,14 +77,8 @@ discovery_enabled on_screen_controls_enabled log_verbose + swap_cross_moon stream_resolution - 360p - 540p - 720p - 1080p stream_fps - 30 - 60 stream_bitrate - Auto (%d) diff --git a/android/app/src/main/res/xml/preferences.xml b/android/app/src/main/res/xml/preferences.xml index 8b67a47..a46302b 100644 --- a/android/app/src/main/res/xml/preferences.xml +++ b/android/app/src/main/res/xml/preferences.xml @@ -14,10 +14,16 @@ app:icon="@drawable/ic_console_simple"/> + +