Skip to content

Commit 965349e

Browse files
committed
feat(core): add a new rotation provider: from display listener
1 parent 0aef739 commit 965349e

File tree

7 files changed

+166
-22
lines changed

7 files changed

+166
-22
lines changed

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,22 @@ streamer.targetRotation =
290290
Surface.ROTATION_90 // Or Surface.ROTATION_0, Surface.ROTATION_180, Surface.ROTATION_270
291291
```
292292

293-
StreamPack comes with a `RotationProvider` that fetches and listens the device rotation: the
294-
`DeviceRotationProvider`. The `DeviceRotationProvider` is backed by the `OrientationEventListener`.
293+
StreamPack comes with 2 `RotationProvider` that fetches and listens the device rotation:
294+
295+
- the `SensorRotationProvider`. The `SensorRotationProvider` is backed by the
296+
`OrientationEventListener`
297+
and it follows the device orientation.
298+
- the `DisplayRotationProvider`. The `DisplayRotationProvider` is backed by the `DisplayManager` and
299+
if orientation is locked, it will return the last known orientation.
300+
301+
You can transform the `RotationProvider` into a `Flow` provider through the `asFlowProvider`
302+
extension.
295303

296304
```kotlin
297305
// Already instantiated streamer
298306
val streamer = DefaultCameraStreamer(context = requireContext())
299307

308+
// For callback based
300309
val listener = object : IRotationProvider.Listener {
301310
override fun onOrientationChanged(rotation: Int) {
302311
streamer.targetRotation = rotation
@@ -306,12 +315,18 @@ rotationProvider.addListener(listener)
306315

307316
// Don't forget to remove the listener when you don't need it anymore
308317
rotationProvider.removeListener(listener)
318+
319+
// For coroutine based
320+
val rotationFlowProvider = rotationProvider.asFlowProvider()
321+
// Then in a coroutine suspend function
322+
rotationFlowProvider.rotationFlow.collect { it ->
323+
streamer.targetRotation = it
324+
}
309325
```
310326

311327
See the `demos/camera` for a complete example.
312328

313-
To only get the device supported orientations, you can use the `DisplayManager.DisplayListener` or
314-
create your own `targetRotation` provider.
329+
You can also create your own `targetRotation` provider.
315330

316331
## Tips
317332

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2024 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.streamers.orientation
17+
18+
import android.content.Context
19+
import android.hardware.display.DisplayManager
20+
import io.github.thibaultbee.streampack.core.internal.utils.extensions.displayRotation
21+
22+
/**
23+
* A [RotationProvider] that provides display rotation.
24+
* It uses [DisplayManager] to get display rotation.
25+
* It follows the orientation of the display only. If device or application are locked in a specific
26+
* orientation, it will not change.
27+
*
28+
* It will notify listeners when the display orientation changes.
29+
*
30+
* @param context The application context
31+
*/
32+
class DisplayRotationProvider(val context: Context) : RotationProvider() {
33+
private val lock = Any()
34+
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
35+
private var _rotation = context.displayRotation
36+
37+
private val displayListener = object : DisplayManager.DisplayListener {
38+
override fun onDisplayAdded(displayId: Int) = Unit
39+
override fun onDisplayRemoved(displayId: Int) = Unit
40+
override fun onDisplayChanged(displayId: Int) {
41+
val newRotation = context.displayRotation
42+
43+
if (_rotation != newRotation) {
44+
_rotation = newRotation
45+
46+
synchronized(lock) {
47+
listeners.forEach { it.onOrientationChanged(newRotation) }
48+
}
49+
}
50+
}
51+
}
52+
53+
override val rotation: Int
54+
get() = _rotation
55+
56+
override fun addListener(listener: IRotationProvider.Listener) {
57+
synchronized(lock) {
58+
val mustRegister = listeners.isEmpty()
59+
super.addListener(listener)
60+
if (mustRegister) {
61+
displayManager.registerDisplayListener(displayListener, null)
62+
}
63+
}
64+
}
65+
66+
override fun removeListener(listener: IRotationProvider.Listener) {
67+
synchronized(lock) {
68+
super.removeListener(listener)
69+
if (listeners.isEmpty()) {
70+
displayManager.unregisterDisplayListener(displayListener)
71+
}
72+
}
73+
}
74+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (C) 2024 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.streamers.orientation
17+
18+
import kotlinx.coroutines.channels.awaitClose
19+
import kotlinx.coroutines.flow.Flow
20+
import kotlinx.coroutines.flow.callbackFlow
21+
22+
23+
/**
24+
* A [RotationProvider] that provides device rotation as a [Flow] instead of a callback.
25+
*
26+
* @param rotationProvider The rotation provider to get rotation from
27+
*/
28+
internal class RotationFlowProvider(private val rotationProvider: RotationProvider) :
29+
IRotationFlowProvider {
30+
override val rotationFlow: Flow<Int> = callbackFlow {
31+
val listener = object : IRotationProvider.Listener {
32+
override fun onOrientationChanged(rotation: Int) {
33+
trySend(rotation)
34+
}
35+
}
36+
rotationProvider.addListener(listener)
37+
awaitClose { rotationProvider.removeListener(listener) }
38+
}
39+
}

core/src/main/java/io/github/thibaultbee/streampack/core/streamers/orientation/RotationProvider.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.github.thibaultbee.streampack.core.streamers.orientation
33
import androidx.annotation.IntRange
44
import io.github.thibaultbee.streampack.core.internal.utils.RotationValue
55
import io.github.thibaultbee.streampack.core.internal.utils.extensions.rotationToDegrees
6+
import kotlinx.coroutines.flow.Flow
67

78
val IRotationProvider.rotationDegrees: Int
89
@IntRange(from = 0, to = 359)
@@ -36,4 +37,14 @@ abstract class RotationProvider : IRotationProvider {
3637
override fun removeListener(listener: IRotationProvider.Listener) {
3738
listeners.remove(listener)
3839
}
40+
}
41+
42+
fun RotationProvider.asFlowProvider(): IRotationFlowProvider = RotationFlowProvider(this)
43+
44+
interface IRotationFlowProvider {
45+
/**
46+
* The rotation in one the [Surface] rotations from the device natural orientation.
47+
*/
48+
@get:RotationValue
49+
val rotationFlow: Flow<Int>
3950
}

core/src/main/java/io/github/thibaultbee/streampack/core/streamers/orientation/DeviceRotationProvider.kt renamed to core/src/main/java/io/github/thibaultbee/streampack/core/streamers/orientation/SensorRotationProvider.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@ import io.github.thibaultbee.streampack.core.internal.utils.extensions.displayRo
2222
import io.github.thibaultbee.streampack.core.utils.extensions.clamp90
2323

2424

25-
class DeviceRotationProvider(val context: Context) : RotationProvider() {
25+
/**
26+
* A [RotationProvider] that provides device rotation.
27+
* It uses [OrientationEventListener] to get device orientation.
28+
* It follows the orientation of the sensor, so it will change when the device is rotated.
29+
*
30+
* It will notify listeners when the device orientation changes.
31+
*
32+
* @param context The application context
33+
*/
34+
class SensorRotationProvider(val context: Context) : RotationProvider() {
2635
private val lock = Any()
2736
private var _rotation = context.displayRotation
2837

@@ -69,7 +78,7 @@ class DeviceRotationProvider(val context: Context) : RotationProvider() {
6978
/**
7079
* Converts orientation degrees to [Surface] rotation.
7180
*/
72-
fun orientationToSurfaceRotation(rotationDegrees: Int): Int {
81+
private fun orientationToSurfaceRotation(rotationDegrees: Int): Int {
7382
return if (rotationDegrees >= 315 || rotationDegrees < 45) {
7483
Surface.ROTATION_0
7584
} else if (rotationDegrees >= 225) {

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/data/rotation/RotationRepository.kt

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package io.github.thibaultbee.streampack.app.data.rotation
22

33
import android.content.Context
4-
import io.github.thibaultbee.streampack.core.streamers.orientation.DeviceRotationProvider
5-
import io.github.thibaultbee.streampack.core.streamers.orientation.IRotationProvider
6-
import kotlinx.coroutines.channels.awaitClose
4+
import io.github.thibaultbee.streampack.core.streamers.orientation.SensorRotationProvider
5+
import io.github.thibaultbee.streampack.core.streamers.orientation.asFlowProvider
76
import kotlinx.coroutines.flow.Flow
8-
import kotlinx.coroutines.flow.callbackFlow
97

108

119
/**
@@ -14,16 +12,14 @@ import kotlinx.coroutines.flow.callbackFlow
1412
class RotationRepository(
1513
context: Context,
1614
) {
17-
private val rotationProvider = DeviceRotationProvider(context)
18-
val rotationFlow: Flow<Int> = callbackFlow {
19-
val listener = object : IRotationProvider.Listener {
20-
override fun onOrientationChanged(rotation: Int) {
21-
trySend(rotation)
22-
}
23-
}
24-
rotationProvider.addListener(listener)
25-
awaitClose { rotationProvider.removeListener(listener) }
26-
}
15+
/**
16+
* A flow of device rotation.
17+
* `SensorRotationProvider` follows the orientation of the sensor, so it will change when the
18+
* device is rotated.
19+
* If the application orientation is locked, you should use `DisplayRotationProvider` instead.
20+
*/
21+
private val rotationProvider = SensorRotationProvider(context).asFlowProvider()
22+
val rotationFlow: Flow<Int> = rotationProvider.rotationFlow
2723

2824
companion object {
2925
@Volatile

services/src/main/java/io/github/thibaultbee/streampack/services/DefaultScreenRecorderService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import androidx.lifecycle.lifecycleScope
3737
import io.github.thibaultbee.streampack.core.internal.utils.extensions.rootCause
3838
import io.github.thibaultbee.streampack.core.logger.Logger
3939
import io.github.thibaultbee.streampack.core.streamers.DefaultScreenRecorderStreamer
40-
import io.github.thibaultbee.streampack.core.streamers.orientation.DeviceRotationProvider
40+
import io.github.thibaultbee.streampack.core.streamers.orientation.SensorRotationProvider
4141
import io.github.thibaultbee.streampack.core.streamers.orientation.IRotationProvider
4242
import io.github.thibaultbee.streampack.services.utils.NotificationUtils
4343
import kotlinx.coroutines.flow.filterNotNull
@@ -78,7 +78,7 @@ abstract class DefaultScreenRecorderService(
7878
protected var streamer: DefaultScreenRecorderStreamer? = null
7979
private set
8080

81-
protected open val rotationProvider: IRotationProvider by lazy { DeviceRotationProvider(this) }
81+
protected open val rotationProvider: IRotationProvider by lazy { SensorRotationProvider(this) }
8282

8383
private val binder = ScreenRecorderServiceBinder()
8484
private val notificationUtils: NotificationUtils by lazy {

0 commit comments

Comments
 (0)