Skip to content

Commit db93661

Browse files
committed
refactor(*): add listener for camera zoom
1 parent ffcbb11 commit db93661

File tree

4 files changed

+88
-17
lines changed

4 files changed

+88
-17
lines changed

core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSettings.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import kotlinx.coroutines.runBlocking
7373
import kotlinx.coroutines.sync.Mutex
7474
import kotlinx.coroutines.sync.withLock
7575
import java.util.concurrent.CancellationException
76+
import java.util.concurrent.CopyOnWriteArrayList
7677
import java.util.concurrent.atomic.AtomicLong
7778

7879

@@ -675,6 +676,8 @@ class CameraSettings internal constructor(
675676
protected val characteristics: CameraCharacteristics,
676677
protected val cameraSettings: CameraSettings
677678
) {
679+
private val listeners: CopyOnWriteArrayList<OnZoomChangedListener> = CopyOnWriteArrayList()
680+
678681
abstract val availableRatioRange: Range<Float>
679682
internal abstract suspend fun getCropSensorRegion(): Rect
680683

@@ -700,6 +703,20 @@ class CameraSettings internal constructor(
700703
}
701704
}
702705

706+
fun addListener(listener: OnZoomChangedListener) {
707+
listeners.add(listener)
708+
}
709+
710+
fun removeListener(listener: OnZoomChangedListener) {
711+
listeners.remove(listener)
712+
}
713+
714+
protected fun notifyZoomListeners(zoomRatio: Float) {
715+
listeners.forEach {
716+
it.onZoomChanged(zoomRatio)
717+
}
718+
}
719+
703720
class CropScalerRegionZoom internal constructor(
704721
characteristics: CameraCharacteristics,
705722
cameraSettings: CameraSettings
@@ -723,15 +740,20 @@ class CameraSettings internal constructor(
723740
override suspend fun setZoomRatio(zoomRatio: Float) {
724741
mutex.withLock {
725742
val clampedValue = zoomRatio.clamp(availableRatioRange)
743+
if (clampedValue == persistentZoomRatio) {
744+
return@withLock
745+
}
746+
persistentZoomRatio = clampedValue
747+
726748
currentCropRect = getCropRegion(
727749
characteristics,
728750
clampedValue
729751
)
730752
cameraSettings.set(
731753
CaptureRequest.SCALER_CROP_REGION, currentCropRect
732754
)
733-
cameraSettings.applyRepeatingSession()
734-
persistentZoomRatio = clampedValue
755+
cameraSettings.applyRepeatingSessionSync()
756+
notifyZoomListeners(clampedValue)
735757
}
736758
}
737759

@@ -791,10 +813,14 @@ class CameraSettings internal constructor(
791813
}
792814

793815
override suspend fun setZoomRatio(zoomRatio: Float) {
816+
if (zoomRatio == getZoomRatio()) {
817+
return
818+
}
794819
cameraSettings.set(
795820
CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio.clamp(availableRatioRange)
796821
)
797822
cameraSettings.applyRepeatingSession()
823+
notifyZoomListeners(zoomRatio)
798824
}
799825

800826
override suspend fun getCropSensorRegion(): Rect {
@@ -817,6 +843,18 @@ class CameraSettings internal constructor(
817843
}
818844
}
819845
}
846+
847+
/**
848+
* Listener for zoom change.
849+
*/
850+
interface OnZoomChangedListener {
851+
/**
852+
* Called when the zoom ratio changes.
853+
*
854+
* @param zoomRatio the zoom ratio
855+
*/
856+
fun onZoomChanged(zoomRatio: Float)
857+
}
820858
}
821859

822860

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/ui/main/PreviewFragment.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import io.github.thibaultbee.streampack.app.R
3434
import io.github.thibaultbee.streampack.app.databinding.MainFragmentBinding
3535
import io.github.thibaultbee.streampack.app.utils.DialogUtils
3636
import io.github.thibaultbee.streampack.app.utils.PermissionManager
37+
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.CameraSettings
3738
import io.github.thibaultbee.streampack.core.interfaces.IStreamer
3839
import io.github.thibaultbee.streampack.core.interfaces.IWithVideoSource
3940
import io.github.thibaultbee.streampack.core.streamers.lifecycle.StreamerViewModelLifeCycleObserver
@@ -182,15 +183,17 @@ class PreviewFragment : Fragment(R.layout.main_fragment) {
182183
private fun inflateStreamerPreview(streamer: IWithVideoSource) {
183184
val preview = binding.preview
184185
// Set camera settings button when camera is started
185-
preview.listener = object : PreviewView.Listener {
186+
preview.listener = object : PreviewView.PreviewListener {
186187
override fun onPreviewStarted() {
187188
Log.i(TAG, "Preview started")
188189
}
190+
}
189191

190-
override fun onZoomRationOnPinchChanged(zoomRatio: Float) {
191-
previewViewModel.onZoomRationOnPinchChanged()
192+
preview.setZoomListener(object : CameraSettings.Zoom.OnZoomChangedListener {
193+
override fun onZoomChanged(zoomRatio: Float) {
194+
previewViewModel.onZoomChanged()
192195
}
193-
}
196+
})
194197

195198
// Wait till streamer exists to set it to the SurfaceView.
196199
lifecycleScope.launch {

demos/camera/src/main/java/io/github/thibaultbee/streampack/app/ui/main/PreviewViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ class PreviewViewModel(private val application: Application) : ObservableViewMod
247247
}
248248
}
249249

250-
fun onZoomRationOnPinchChanged() {
250+
fun onZoomChanged() {
251251
notifyPropertyChanged(BR.zoomRatio)
252252
}
253253

ui/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/PreviewView.kt

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import androidx.camera.viewfinder.core.ScaleType
3535
import androidx.camera.viewfinder.core.ViewfinderSurfaceRequest
3636
import androidx.camera.viewfinder.core.populateFromCharacteristics
3737
import io.github.thibaultbee.streampack.core.elements.sources.video.IPreviewableSource
38+
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.CameraSettings
3839
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.CameraSettings.FocusMetering.Companion.DEFAULT_AUTO_CANCEL_DURATION_MS
3940
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.ICameraSource
4041
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.getCameraCharacteristics
@@ -108,9 +109,11 @@ class PreviewView @JvmOverloads constructor(
108109
}
109110

110111
/**
111-
* The [Listener] to listen to specific view events.
112+
* The [PreviewListener] to listen to specific view events.
112113
*/
113-
var listener: Listener? = null
114+
var listener: PreviewListener? = null
115+
116+
private var zoomListener: CameraSettings.Zoom.OnZoomChangedListener? = null
114117

115118
private var touchUpEvent: MotionEvent? = null
116119

@@ -176,12 +179,46 @@ class PreviewView @JvmOverloads constructor(
176179
}
177180
if (newVideoSource is IPreviewableSource) {
178181
attachToStreamerIfReady(true)
182+
zoomListener?.let {
183+
registerZoomListener(it)
184+
}
179185
}
180186
}
181187
}
182188
}
183189
}
184190

191+
/**
192+
* Sets the [CameraSettings.Zoom.OnZoomChangedListener] to listen to zoom changes.
193+
*
194+
* @param listener the [CameraSettings.Zoom.OnZoomChangedListener] to listen to zoom changes.
195+
*/
196+
fun setZoomListener(listener: CameraSettings.Zoom.OnZoomChangedListener?) {
197+
if (listener == null) {
198+
unregisterZoomListener()
199+
} else {
200+
registerZoomListener(listener)
201+
}
202+
zoomListener = listener
203+
}
204+
205+
private fun registerZoomListener(listener: CameraSettings.Zoom.OnZoomChangedListener) {
206+
val source = streamer?.videoInput?.sourceFlow?.value
207+
if (source is ICameraSource) {
208+
source.settings.zoom.addListener(listener)
209+
}
210+
}
211+
212+
private fun unregisterZoomListener() {
213+
zoomListener?.let {
214+
val source = streamer?.videoInput?.sourceFlow?.value
215+
if (source is ICameraSource) {
216+
source.settings.zoom.removeListener(it)
217+
}
218+
}
219+
}
220+
221+
185222
/**
186223
* Sets the [IWithVideoSource] to preview.
187224
*
@@ -504,7 +541,6 @@ class PreviewView @JvmOverloads constructor(
504541
mutex.withLock {
505542
val zoom = source.settings.zoom
506543
zoom.onPinch(scaleFactor)
507-
listener?.onZoomRationOnPinchChanged(zoom.getZoomRatio())
508544
}
509545
}
510546
return true
@@ -518,7 +554,7 @@ class PreviewView @JvmOverloads constructor(
518554
/**
519555
* A listener for the [PreviewView].
520556
*/
521-
interface Listener {
557+
interface PreviewListener {
522558
/**
523559
* Called when the preview is started.
524560
*/
@@ -528,12 +564,6 @@ class PreviewView @JvmOverloads constructor(
528564
* Called when the preview failed to start.
529565
*/
530566
fun onPreviewFailed(t: Throwable) {}
531-
532-
/**
533-
* Called when the zoom ratio is changed.
534-
* @param zoomRatio the new zoom ratio
535-
*/
536-
fun onZoomRationOnPinchChanged(zoomRatio: Float) {}
537567
}
538568

539569
/**

0 commit comments

Comments
 (0)