Skip to content

Commit a76c80b

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

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

@@ -684,6 +685,8 @@ class CameraSettings internal constructor(
684685
protected val characteristics: CameraCharacteristics,
685686
protected val cameraSettings: CameraSettings
686687
) {
688+
private val listeners: CopyOnWriteArrayList<OnZoomChangedListener> = CopyOnWriteArrayList()
689+
687690
abstract val availableRatioRange: Range<Float>
688691
internal abstract suspend fun getCropSensorRegion(): Rect
689692

@@ -709,6 +712,20 @@ class CameraSettings internal constructor(
709712
}
710713
}
711714

715+
fun addListener(listener: OnZoomChangedListener) {
716+
listeners.add(listener)
717+
}
718+
719+
fun removeListener(listener: OnZoomChangedListener) {
720+
listeners.remove(listener)
721+
}
722+
723+
protected fun notifyZoomListeners(zoomRatio: Float) {
724+
listeners.forEach {
725+
it.onZoomChanged(zoomRatio)
726+
}
727+
}
728+
712729
class CropScalerRegionZoom internal constructor(
713730
characteristics: CameraCharacteristics,
714731
cameraSettings: CameraSettings
@@ -732,15 +749,20 @@ class CameraSettings internal constructor(
732749
override suspend fun setZoomRatio(zoomRatio: Float) {
733750
mutex.withLock {
734751
val clampedValue = zoomRatio.clamp(availableRatioRange)
752+
if (clampedValue == persistentZoomRatio) {
753+
return@withLock
754+
}
755+
persistentZoomRatio = clampedValue
756+
735757
currentCropRect = getCropRegion(
736758
characteristics,
737759
clampedValue
738760
)
739761
cameraSettings.set(
740762
CaptureRequest.SCALER_CROP_REGION, currentCropRect
741763
)
742-
cameraSettings.applyRepeatingSession()
743-
persistentZoomRatio = clampedValue
764+
cameraSettings.applyRepeatingSessionSync()
765+
notifyZoomListeners(clampedValue)
744766
}
745767
}
746768

@@ -800,10 +822,14 @@ class CameraSettings internal constructor(
800822
}
801823

802824
override suspend fun setZoomRatio(zoomRatio: Float) {
825+
if (zoomRatio == getZoomRatio()) {
826+
return
827+
}
803828
cameraSettings.set(
804829
CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio.clamp(availableRatioRange)
805830
)
806831
cameraSettings.applyRepeatingSession()
832+
notifyZoomListeners(zoomRatio)
807833
}
808834

809835
override suspend fun getCropSensorRegion(): Rect {
@@ -826,6 +852,18 @@ class CameraSettings internal constructor(
826852
}
827853
}
828854
}
855+
856+
/**
857+
* Listener for zoom change.
858+
*/
859+
interface OnZoomChangedListener {
860+
/**
861+
* Called when the zoom ratio changes.
862+
*
863+
* @param zoomRatio the zoom ratio
864+
*/
865+
fun onZoomChanged(zoomRatio: Float)
866+
}
829867
}
830868

831869

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)