Skip to content

Commit

Permalink
feat(core): add a new rotation provider: from display listener
Browse files Browse the repository at this point in the history
  • Loading branch information
ThibaultBee committed Dec 13, 2024
1 parent 0aef739 commit 06541a1
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 Thibault B.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.thibaultbee.streampack.core.streamers.orientation

import android.content.Context
import android.hardware.display.DisplayManager
import io.github.thibaultbee.streampack.core.internal.utils.extensions.displayRotation

/**
* A [RotationProvider] that provides display rotation.
* It uses [DisplayManager] to get display rotation.
* It follows the orientation of the display only. If device or application are locked in a specific
* orientation, it will not change.
*
* It will notify listeners when the display orientation changes.
*
* @param context The application context
*/
class DisplayRotationProvider(val context: Context) : RotationProvider() {
private val lock = Any()
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private var _rotation = context.displayRotation

private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) {
val newRotation = context.displayRotation

if (_rotation != newRotation) {
_rotation = newRotation

synchronized(lock) {
listeners.forEach { it.onOrientationChanged(newRotation) }
}
}
}
}

override val rotation: Int
get() = _rotation

override fun addListener(listener: IRotationProvider.Listener) {
synchronized(lock) {
val mustRegister = listeners.isEmpty()
super.addListener(listener)
if (mustRegister) {
displayManager.registerDisplayListener(displayListener, null)
}
}
}

override fun removeListener(listener: IRotationProvider.Listener) {
synchronized(lock) {
super.removeListener(listener)
if (listeners.isEmpty()) {
displayManager.unregisterDisplayListener(displayListener)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 Thibault B.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.thibaultbee.streampack.core.streamers.orientation

import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow


/**
* A [RotationProvider] that provides device rotation as a [Flow] instead of a callback.
*
* @param rotationProvider The rotation provider to get rotation from
*/
class RotationFlowProvider(private val rotationProvider: RotationProvider) : IRotationFlowProvider {
override val rotationFlow: Flow<Int> = callbackFlow {
val listener = object : IRotationProvider.Listener {
override fun onOrientationChanged(rotation: Int) {
trySend(rotation)
}
}
rotationProvider.addListener(listener)
awaitClose { rotationProvider.removeListener(listener) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.thibaultbee.streampack.core.streamers.orientation
import androidx.annotation.IntRange
import io.github.thibaultbee.streampack.core.internal.utils.RotationValue
import io.github.thibaultbee.streampack.core.internal.utils.extensions.rotationToDegrees
import kotlinx.coroutines.flow.Flow

val IRotationProvider.rotationDegrees: Int
@IntRange(from = 0, to = 359)
Expand Down Expand Up @@ -36,4 +37,12 @@ abstract class RotationProvider : IRotationProvider {
override fun removeListener(listener: IRotationProvider.Listener) {
listeners.remove(listener)
}
}

interface IRotationFlowProvider {
/**
* The rotation in one the [Surface] rotations from the device natural orientation.
*/
@get:RotationValue
val rotationFlow: Flow<Int>
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ import io.github.thibaultbee.streampack.core.internal.utils.extensions.displayRo
import io.github.thibaultbee.streampack.core.utils.extensions.clamp90


class DeviceRotationProvider(val context: Context) : RotationProvider() {
/**
* A [RotationProvider] that provides device rotation.
* It uses [OrientationEventListener] to get device orientation.
* It follows the orientation of the sensor, so it will change when the device is rotated.
*
* It will notify listeners when the device orientation changes.
*
* @param context The application context
*/
class SensorRotationProvider(val context: Context) : RotationProvider() {
private val lock = Any()
private var _rotation = context.displayRotation

Expand Down Expand Up @@ -69,7 +78,7 @@ class DeviceRotationProvider(val context: Context) : RotationProvider() {
/**
* Converts orientation degrees to [Surface] rotation.
*/
fun orientationToSurfaceRotation(rotationDegrees: Int): Int {
private fun orientationToSurfaceRotation(rotationDegrees: Int): Int {
return if (rotationDegrees >= 315 || rotationDegrees < 45) {
Surface.ROTATION_0
} else if (rotationDegrees >= 225) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package io.github.thibaultbee.streampack.app.data.rotation

import android.content.Context
import io.github.thibaultbee.streampack.core.streamers.orientation.DeviceRotationProvider
import io.github.thibaultbee.streampack.core.streamers.orientation.IRotationProvider
import kotlinx.coroutines.channels.awaitClose
import io.github.thibaultbee.streampack.core.streamers.orientation.DisplayRotationProvider
import io.github.thibaultbee.streampack.core.streamers.orientation.RotationFlowProvider
import io.github.thibaultbee.streampack.core.streamers.orientation.SensorRotationProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow


/**
Expand All @@ -14,16 +13,8 @@ import kotlinx.coroutines.flow.callbackFlow
class RotationRepository(
context: Context,
) {
private val rotationProvider = DeviceRotationProvider(context)
val rotationFlow: Flow<Int> = callbackFlow {
val listener = object : IRotationProvider.Listener {
override fun onOrientationChanged(rotation: Int) {
trySend(rotation)
}
}
rotationProvider.addListener(listener)
awaitClose { rotationProvider.removeListener(listener) }
}
private val rotationProvider = RotationFlowProvider(DisplayRotationProvider(context))
val rotationFlow: Flow<Int> = rotationProvider.rotationFlow

companion object {
@Volatile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import androidx.lifecycle.lifecycleScope
import io.github.thibaultbee.streampack.core.internal.utils.extensions.rootCause
import io.github.thibaultbee.streampack.core.logger.Logger
import io.github.thibaultbee.streampack.core.streamers.DefaultScreenRecorderStreamer
import io.github.thibaultbee.streampack.core.streamers.orientation.DeviceRotationProvider
import io.github.thibaultbee.streampack.core.streamers.orientation.SensorRotationProvider
import io.github.thibaultbee.streampack.core.streamers.orientation.IRotationProvider
import io.github.thibaultbee.streampack.services.utils.NotificationUtils
import kotlinx.coroutines.flow.filterNotNull
Expand Down Expand Up @@ -78,7 +78,7 @@ abstract class DefaultScreenRecorderService(
protected var streamer: DefaultScreenRecorderStreamer? = null
private set

protected open val rotationProvider: IRotationProvider by lazy { DeviceRotationProvider(this) }
protected open val rotationProvider: IRotationProvider by lazy { SensorRotationProvider(this) }

private val binder = ScreenRecorderServiceBinder()
private val notificationUtils: NotificationUtils by lazy {
Expand Down

0 comments on commit 06541a1

Please sign in to comment.