mirror of
https://git.sr.ht/~thestr4ng3r/chiaki
synced 2025-08-19 21:13:12 -07:00
Refactor Android Input and add Face Button Swap
This commit is contained in:
parent
863b6ce189
commit
bd02acb5a0
9 changed files with 311 additions and 268 deletions
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<StreamState>(StreamStateIdle)
|
|
||||||
val state: LiveData<StreamState> 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -79,6 +79,11 @@ class Preferences(context: Context)
|
||||||
get() = sharedPreferences.getBoolean(logVerboseKey, false)
|
get() = sharedPreferences.getBoolean(logVerboseKey, false)
|
||||||
set(value) { sharedPreferences.edit().putBoolean(logVerboseKey, value).apply() }
|
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)
|
val resolutionKey get() = resources.getString(R.string.preferences_resolution_key)
|
||||||
var resolution
|
var resolution
|
||||||
get() = sharedPreferences.getString(resolutionKey, resolutionDefault.value)?.let { value ->
|
get() = sharedPreferences.getString(resolutionKey, resolutionDefault.value)?.let { value ->
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<StreamState>(StreamStateIdle)
|
||||||
|
val state: LiveData<StreamState> 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,14 +20,9 @@ package com.metallic.chiaki.stream
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
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.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -38,13 +33,11 @@ import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.metallic.chiaki.*
|
|
||||||
import com.metallic.chiaki.R
|
import com.metallic.chiaki.R
|
||||||
import com.metallic.chiaki.common.Preferences
|
import com.metallic.chiaki.common.Preferences
|
||||||
import com.metallic.chiaki.common.ext.viewModelFactory
|
import com.metallic.chiaki.common.ext.viewModelFactory
|
||||||
import com.metallic.chiaki.lib.ConnectInfo
|
import com.metallic.chiaki.lib.ConnectInfo
|
||||||
import com.metallic.chiaki.lib.LoginPinRequestEvent
|
import com.metallic.chiaki.session.*
|
||||||
import com.metallic.chiaki.lib.QuitReason
|
|
||||||
import com.metallic.chiaki.touchcontrols.TouchControlsFragment
|
import com.metallic.chiaki.touchcontrols.TouchControlsFragment
|
||||||
import kotlinx.android.synthetic.main.activity_stream.*
|
import kotlinx.android.synthetic.main.activity_stream.*
|
||||||
|
|
||||||
|
@ -68,18 +61,17 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
|
||||||
{
|
{
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory { StreamViewModel(Preferences(this)) })[StreamViewModel::class.java]
|
val connectInfo = intent.getParcelableExtra<ConnectInfo>(EXTRA_CONNECT_INFO)
|
||||||
if(!viewModel.isInitialized)
|
if(connectInfo == null)
|
||||||
{
|
{
|
||||||
val connectInfo = intent.getParcelableExtra<ConnectInfo>(EXTRA_CONNECT_INFO)
|
finish()
|
||||||
if(connectInfo == null)
|
return
|
||||||
{
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
viewModel.init(connectInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(this, viewModelFactory {
|
||||||
|
StreamViewModel(Preferences(this), connectInfo)
|
||||||
|
})[StreamViewModel::class.java]
|
||||||
|
|
||||||
setContentView(R.layout.activity_stream)
|
setContentView(R.layout.activity_stream)
|
||||||
window.decorView.setOnSystemUiVisibilityChangeListener(this)
|
window.decorView.setOnSystemUiVisibilityChangeListener(this)
|
||||||
|
|
||||||
|
@ -104,7 +96,7 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
|
||||||
super.onAttachFragment(fragment)
|
super.onAttachFragment(fragment)
|
||||||
if(fragment is TouchControlsFragment)
|
if(fragment is TouchControlsFragment)
|
||||||
{
|
{
|
||||||
fragment.controllerStateCallback = viewModel.session::updateTouchControllerState
|
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it }
|
||||||
fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled
|
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 dispatchKeyEvent(event: KeyEvent) = viewModel.input.dispatchKeyEvent(event) || super.dispatchKeyEvent(event)
|
||||||
override fun onGenericMotionEvent(event: MotionEvent) = viewModel.session.onGenericMotionEvent(event) || super.onGenericMotionEvent(event)
|
override fun onGenericMotionEvent(event: MotionEvent) = viewModel.input.onGenericMotionEvent(event) || super.onGenericMotionEvent(event)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,27 +20,20 @@ package com.metallic.chiaki.stream
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
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.common.Preferences
|
||||||
import com.metallic.chiaki.lib.*
|
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
|
private var _session: StreamSession? = null
|
||||||
val session: StreamSession get() = _session ?: throw UninitializedPropertyAccessException("StreamViewModel not initialized")
|
val input = StreamInput(preferences)
|
||||||
val isInitialized get() = connectInfo != null
|
val session = StreamSession(connectInfo, input)
|
||||||
|
|
||||||
private var _onScreenControlsEnabled = MutableLiveData<Boolean>(preferences.onScreenControlsEnabled)
|
private var _onScreenControlsEnabled = MutableLiveData<Boolean>(preferences.onScreenControlsEnabled)
|
||||||
val onScreenControlsEnabled: LiveData<Boolean> get() = _onScreenControlsEnabled
|
val onScreenControlsEnabled: LiveData<Boolean> get() = _onScreenControlsEnabled
|
||||||
|
|
||||||
fun init(connectInfo: ConnectInfo)
|
|
||||||
{
|
|
||||||
if(isInitialized)
|
|
||||||
return
|
|
||||||
_session = StreamSession(connectInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared()
|
override fun onCleared()
|
||||||
{
|
{
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|
9
android/app/src/main/res/drawable/ic_gamepad.xml
Normal file
9
android/app/src/main/res/drawable/ic_gamepad.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.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:attr/textColorPrimary"
|
||||||
|
android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
|
||||||
|
</vector>
|
|
@ -58,6 +58,15 @@
|
||||||
<string name="preferences_bitrate_title">Bitrate</string>
|
<string name="preferences_bitrate_title">Bitrate</string>
|
||||||
<string name="preferences_log_verbose_title">Verbose Logging</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_log_verbose_summary">Warning: This logs a LOT! Don\'t enable for regular use.</string>
|
||||||
|
<string name="preferences_resolution_title_360p">360p</string>
|
||||||
|
<string name="preferences_resolution_title_540p">540p</string>
|
||||||
|
<string name="preferences_resolution_title_720p">720p</string>
|
||||||
|
<string name="preferences_resolution_title_1080p">1080p</string>
|
||||||
|
<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_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>
|
<string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string>
|
||||||
<string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string>
|
<string name="alert_message_delete_manual_host">Are you sure you want to delete the console entry for %s?</string>
|
||||||
<string name="action_keep">Keep</string>
|
<string name="action_keep">Keep</string>
|
||||||
|
@ -68,14 +77,8 @@
|
||||||
<string name="preferences_discovery_enabled_key">discovery_enabled</string>
|
<string name="preferences_discovery_enabled_key">discovery_enabled</string>
|
||||||
<string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string>
|
<string name="preferences_on_screen_controls_enabled_key">on_screen_controls_enabled</string>
|
||||||
<string name="preferences_log_verbose_key">log_verbose</string>
|
<string name="preferences_log_verbose_key">log_verbose</string>
|
||||||
|
<string name="preferences_swap_cross_moon_key">swap_cross_moon</string>
|
||||||
<string name="preferences_resolution_key">stream_resolution</string>
|
<string name="preferences_resolution_key">stream_resolution</string>
|
||||||
<string name="preferences_resolution_title_360p">360p</string>
|
|
||||||
<string name="preferences_resolution_title_540p">540p</string>
|
|
||||||
<string name="preferences_resolution_title_720p">720p</string>
|
|
||||||
<string name="preferences_resolution_title_1080p">1080p</string>
|
|
||||||
<string name="preferences_fps_key">stream_fps</string>
|
<string name="preferences_fps_key">stream_fps</string>
|
||||||
<string name="preferences_fps_title_30">30</string>
|
|
||||||
<string name="preferences_fps_title_60">60</string>
|
|
||||||
<string name="preferences_bitrate_key">stream_bitrate</string>
|
<string name="preferences_bitrate_key">stream_bitrate</string>
|
||||||
<string name="preferences_bitrate_auto">Auto (%d)</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -14,10 +14,16 @@
|
||||||
app:icon="@drawable/ic_console_simple"/>
|
app:icon="@drawable/ic_console_simple"/>
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
app:key="log_verbose"
|
app:key="@string/preferences_log_verbose_key"
|
||||||
app:title="@string/preferences_log_verbose_title"
|
app:title="@string/preferences_log_verbose_title"
|
||||||
app:summary="@string/preferences_log_verbose_summary"
|
app:summary="@string/preferences_log_verbose_summary"
|
||||||
app:icon="@drawable/ic_log" />
|
app:icon="@drawable/ic_log" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="@string/preferences_swap_cross_moon_key"
|
||||||
|
app:title="@string/preferences_swap_cross_moon_title"
|
||||||
|
app:summary="@string/preferences_swap_cross_moon_summary"
|
||||||
|
app:icon="@drawable/ic_gamepad" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue