diff --git a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/IPreviewableSource.kt b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/IPreviewableSource.kt index 88f587880..91b7d3116 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/IPreviewableSource.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/IPreviewableSource.kt @@ -20,6 +20,7 @@ import android.view.Surface import android.view.SurfaceHolder import android.view.SurfaceView import android.view.TextureView +import io.github.thibaultbee.streampack.core.elements.processing.video.source.ISourceInfoProvider import io.github.thibaultbee.streampack.core.interfaces.setPreview import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer import kotlinx.coroutines.flow.StateFlow @@ -31,6 +32,12 @@ import kotlinx.coroutines.sync.Mutex * The methods of this interface should be called in the [previewMutex]. */ interface IPreviewableSource { + /** + * Orientation provider of the capture source. + * It is used to orientate the frame according to the source orientation. + */ + val infoProviderFlow: StateFlow + /** * Mutex for the preview. * Use it when you have to synchronise access to the preview. diff --git a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraInfoProvider.kt b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraInfoProvider.kt index 531d6a437..841951541 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraInfoProvider.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraInfoProvider.kt @@ -16,7 +16,6 @@ package io.github.thibaultbee.streampack.core.elements.sources.video.camera import android.hardware.camera2.CameraCharacteristics -import android.hardware.camera2.CameraManager import android.util.Size import androidx.annotation.IntRange import io.github.thibaultbee.streampack.core.elements.processing.video.source.ISourceInfoProvider @@ -25,10 +24,8 @@ import io.github.thibaultbee.streampack.core.elements.utils.RotationValue import io.github.thibaultbee.streampack.core.elements.utils.extensions.rotationToDegrees internal fun CameraInfoProvider( - cameraManager: CameraManager, - cameraId: String + characteristics: CameraCharacteristics ): CameraInfoProvider { - val characteristics = cameraManager.getCameraCharacteristics(cameraId) val rotationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0 val facingDirection = characteristics.get(CameraCharacteristics.LENS_FACING) return CameraInfoProvider(rotationDegrees, facingDirection = facingDirection) diff --git a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSource.kt b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSource.kt index 38c607749..23844098f 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSource.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSource.kt @@ -87,7 +87,7 @@ internal class CameraSource( } - override val infoProviderFlow = MutableStateFlow(CameraInfoProvider(manager, cameraId)) + override val infoProviderFlow = MutableStateFlow(CameraInfoProvider(characteristics)) // States private val _isStreamingFlow = MutableStateFlow(false) diff --git a/demos/camera/build.gradle.kts b/demos/camera/build.gradle.kts index 72c1848e5..ffc0af858 100644 --- a/demos/camera/build.gradle.kts +++ b/demos/camera/build.gradle.kts @@ -10,6 +10,8 @@ android { defaultConfig { applicationId = "io.github.thibaultbee.streampack.sample" + + minSdk = 23 } buildFeatures { viewBinding = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17d69f9cc..9bbbd2ecc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ guava = "33.5.0-android" videoApiClient = "1.6.7" androidxActivity = "1.10.1" androidxAppcompat = "1.7.1" -androidxCamera = "1.4.0-alpha13" +androidxCamera = "1.5.2" androidxConstraintlayout = "2.2.1" androidxCore = "1.17.0" androidxDatabinding = "8.13.0" diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 32d60dd2a..f0334be06 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -1,3 +1,5 @@ +import utils.AndroidVersions + plugins { id(libs.plugins.android.library.get().pluginId) id(libs.plugins.kotlin.android.get().pluginId) @@ -8,6 +10,10 @@ description = "UI components for StreamPack." android { namespace = "io.github.thibaultbee.streampack.ui" + + defaultConfig { + minSdk = 23 + } } dependencies { diff --git a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/PreviewView.kt b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/PreviewView.kt index 88dc5a1eb..4259b2895 100644 --- a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/PreviewView.kt +++ b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/PreviewView.kt @@ -29,15 +29,15 @@ import android.view.SurfaceHolder import android.view.ViewConfiguration import android.view.ViewGroup import android.widget.FrameLayout -import androidx.camera.viewfinder.CameraViewfinder -import androidx.camera.viewfinder.CameraViewfinderExt.requestSurface import androidx.camera.viewfinder.core.ScaleType +import androidx.camera.viewfinder.core.TransformationInfo import androidx.camera.viewfinder.core.ViewfinderSurfaceRequest -import androidx.camera.viewfinder.core.populateFromCharacteristics +import androidx.camera.viewfinder.core.ViewfinderSurfaceSession +import androidx.camera.viewfinder.view.ViewfinderView +import androidx.camera.viewfinder.view.requestSurfaceSession import io.github.thibaultbee.streampack.core.elements.sources.video.IPreviewableSource import io.github.thibaultbee.streampack.core.elements.sources.video.camera.CameraSettings.FocusMetering.Companion.DEFAULT_AUTO_CANCEL_DURATION_MS import io.github.thibaultbee.streampack.core.elements.sources.video.camera.ICameraSource -import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.getCameraCharacteristics import io.github.thibaultbee.streampack.core.elements.utils.ConflatedJob import io.github.thibaultbee.streampack.core.elements.utils.OrientationUtils import io.github.thibaultbee.streampack.core.elements.utils.extensions.runningHistoryNotNull @@ -70,9 +70,9 @@ import kotlinx.coroutines.withContext class PreviewView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : FrameLayout(context, attrs, defStyle) { - private val viewfinder = CameraViewfinder(context, attrs, defStyle) + private val viewfinder = ViewfinderView(context, attrs, defStyle) - private var viewfinderSurfaceRequest: ViewfinderSurfaceRequest? = null + private var surfaceRequestSession: ViewfinderSurfaceSession? = null /** * Enables zoom on pinch gesture. @@ -337,8 +337,6 @@ class PreviewView @JvmOverloads constructor( /** * Requests a [Surface] for the size and the current streamer video source. - * - * The [Surface] is emit to the [surfaceFlow]. */ private fun startPreview(size: Size) { Logger.d(TAG, "Requesting surface for $size") @@ -352,8 +350,6 @@ class PreviewView @JvmOverloads constructor( /** * Requests a [Surface] for the size and the [videoSource]. - * - * The [Surface] is emit to the [surfaceFlow]. */ private fun startPreview( size: Size, @@ -364,27 +360,27 @@ class PreviewView @JvmOverloads constructor( return } val previewSize = getPreviewSize(videoSource, size) - val builder = ViewfinderSurfaceRequest.Builder(previewSize) - if (videoSource is ICameraSource) { - val cameraCharacteristics = - context.getCameraCharacteristics(videoSource.cameraId) - builder.populateFromCharacteristics(cameraCharacteristics) - } else { - val rotationDegrees = OrientationUtils.getSurfaceRotationDegrees(display.rotation) - builder.setSourceOrientation(rotationDegrees) - } + val request = ViewfinderSurfaceRequest(previewSize.width, previewSize.height) defaultScope.launch { - startPreview(videoSource, builder) + startPreview(videoSource, request) } } private suspend fun startPreview( - videoSource: IPreviewableSource, viewfinderBuilder: ViewfinderSurfaceRequest.Builder + videoSource: IPreviewableSource, viewfinderRequest: ViewfinderSurfaceRequest ) { try { - Logger.d(TAG, "Starting preview") videoSource.previewMutex.withLock { - val surface = requestSurface(viewfinderBuilder) + val surface = requestSurface(viewfinderRequest) + Logger.d(TAG, "Settings transformation") + viewfinder.transformationInfo = TransformationInfo( + sourceRotation = videoSource.infoProviderFlow.value.getRelativeRotationDegrees( + display.rotation, + false + ) + ) + + Logger.d(TAG, "Starting preview") videoSource.startPreview(surface) } Logger.d(TAG, "Preview started") @@ -399,13 +395,13 @@ class PreviewView @JvmOverloads constructor( * Use [requestSurface] instead. */ private suspend fun requestSurface( - viewfinderBuilder: ViewfinderSurfaceRequest.Builder + viewfinderRequest: ViewfinderSurfaceRequest ): Surface { return withContext(mainDispatcher) { - val viewfinderRequest = viewfinderBuilder.build().apply { - viewfinderSurfaceRequest = this + val requestSession = viewfinder.requestSurfaceSession(viewfinderRequest).apply { + surfaceRequestSession = this } - viewfinder.requestSurface(viewfinderRequest) + requestSession.surface } } @@ -421,8 +417,8 @@ class PreviewView @JvmOverloads constructor( Logger.d(TAG, "Preview stopped") } } - viewfinderSurfaceRequest?.markSurfaceSafeToRelease() - viewfinderSurfaceRequest = null + surfaceRequestSession?.close() + surfaceRequestSession = null } diff --git a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/StreamerExtensions.kt b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/StreamerExtensions.kt index 7134c77cd..138282a4d 100644 --- a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/StreamerExtensions.kt +++ b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/StreamerExtensions.kt @@ -1,40 +1,40 @@ package io.github.thibaultbee.streampack.ui.views import android.util.Size -import androidx.camera.viewfinder.CameraViewfinder -import androidx.camera.viewfinder.core.ViewfinderSurfaceRequest +import androidx.camera.viewfinder.core.ViewfinderSurfaceSession +import androidx.camera.viewfinder.view.ViewfinderView import io.github.thibaultbee.streampack.core.elements.sources.video.IPreviewableSource import io.github.thibaultbee.streampack.core.interfaces.IWithVideoSource /** - * Set preview on a [CameraViewfinder] + * Sets preview on a [ViewfinderView] * - * @param viewfinder The [CameraViewfinder] to set as preview + * @param viewfinder The [ViewfinderView] to set as preview * @param previewSize The size of the preview - * @return The [ViewfinderSurfaceRequest] used to set the preview. Use it to call [ViewfinderSurfaceRequest.markSurfaceSafeToRelease]. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. * @throws [IllegalStateException] if the video source is not previewable */ suspend fun IWithVideoSource.setPreview( - viewfinder: CameraViewfinder, + viewfinder: ViewfinderView, previewSize: Size -): ViewfinderSurfaceRequest { +): ViewfinderSurfaceSession { val videoSource = videoInput?.sourceFlow?.value as? IPreviewableSource ?: throw IllegalStateException("Video source is not previewable") return videoSource.setPreview(viewfinder, previewSize) } /** - * Start preview on a [CameraViewfinder] + * Starts preview on a [ViewfinderView] * - * @param viewfinder The [CameraViewfinder] to set as preview + * @param viewfinder The [ViewfinderView] to set as preview * @param previewSize The size of the preview - * @return The [ViewfinderSurfaceRequest] used to set the preview. Use it to call [ViewfinderSurfaceRequest.markSurfaceSafeToRelease] after [IWithVideoSource.stopPreview]. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. * @throws [IllegalStateException] if the video source is not previewable */ suspend fun IWithVideoSource.startPreview( - viewfinder: CameraViewfinder, + viewfinder: ViewfinderView, previewSize: Size -): ViewfinderSurfaceRequest { +): ViewfinderSurfaceSession { val videoSource = videoInput?.sourceFlow?.value as? IPreviewableSource ?: throw IllegalStateException("Video source is not previewable") return videoSource.startPreview(viewfinder, previewSize) diff --git a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/VideoSourceExtensions.kt b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/VideoSourceExtensions.kt index f602f7f3e..ef24e0dab 100644 --- a/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/VideoSourceExtensions.kt +++ b/ui/src/main/java/io/github/thibaultbee/streampack/ui/views/VideoSourceExtensions.kt @@ -16,48 +16,70 @@ package io.github.thibaultbee.streampack.ui.views import android.util.Size -import androidx.camera.viewfinder.CameraViewfinder -import androidx.camera.viewfinder.CameraViewfinderExt.requestSurface import androidx.camera.viewfinder.core.ViewfinderSurfaceRequest -import androidx.camera.viewfinder.core.populateFromCharacteristics +import androidx.camera.viewfinder.core.ViewfinderSurfaceSession +import androidx.camera.viewfinder.view.ViewfinderView +import androidx.camera.viewfinder.view.requestSurfaceSession import io.github.thibaultbee.streampack.core.elements.sources.video.IPreviewableSource -import io.github.thibaultbee.streampack.core.elements.sources.video.camera.ICameraSource -import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.getCameraCharacteristics /** - * Start preview on a [CameraViewfinder] + * Sets preview on a [ViewfinderView] * - * @param viewfinder The [CameraViewfinder] to set as preview + * @param viewfinderView The [ViewfinderView] to set as preview * @param previewSize The size of the preview - * @return The [ViewfinderSurfaceRequest] used to set the preview. Use it to call [ViewfinderSurfaceRequest.markSurfaceSafeToRelease] after [stopPreview]. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. */ -suspend fun IPreviewableSource.startPreview( - viewfinder: CameraViewfinder, +suspend fun IPreviewableSource.setPreview( + viewfinderView: ViewfinderView, previewSize: Size -): ViewfinderSurfaceRequest { - val request = setPreview(viewfinder, previewSize) - startPreview() - return request +) = setPreview(viewfinderView, ViewfinderSurfaceRequest(previewSize.width, previewSize.height)) + +/** + * Sets preview on a [ViewfinderView] + * + * @param viewfinderView The [ViewfinderView] to set as preview + * @param surfaceRequest The [ViewfinderSurfaceRequest] used to set the preview. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. + */ +suspend fun IPreviewableSource.setPreview( + viewfinderView: ViewfinderView, + surfaceRequest: ViewfinderSurfaceRequest +): ViewfinderSurfaceSession { + val surfaceSession = viewfinderView.requestSurfaceSession( + surfaceRequest + ) + setPreview(surfaceSession.surface) + return surfaceSession } /** - * Set preview on a [CameraViewfinder] + * Starts preview on a [ViewfinderView] * - * @param viewfinder The [CameraViewfinder] to set as preview + * @param viewfinderView The [ViewfinderView] to set as preview * @param previewSize The size of the preview - * @return The [ViewfinderSurfaceRequest] used to set the preview. Use it to call [ViewfinderSurfaceRequest.markSurfaceSafeToRelease]. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. */ -suspend fun IPreviewableSource.setPreview( - viewfinder: CameraViewfinder, +suspend fun IPreviewableSource.startPreview( + viewfinderView: ViewfinderView, previewSize: Size -): ViewfinderSurfaceRequest { - val builder = ViewfinderSurfaceRequest.Builder(previewSize) - val request = if (this is ICameraSource) { - val cameraCharacteristics = viewfinder.context.getCameraCharacteristics(cameraId) - builder.populateFromCharacteristics(cameraCharacteristics).build() - } else { - builder.build() - } - setPreview(viewfinder.requestSurface(request)) - return request +): ViewfinderSurfaceSession { + val surfaceSession = setPreview(viewfinderView, previewSize) + startPreview() + return surfaceSession +} + +/** + * Starts preview on a [ViewfinderView] + * + * @param viewfinderView The [ViewfinderView] to set as preview + * @param surfaceRequest The [ViewfinderSurfaceRequest] used to set the preview. + * @return The [ViewfinderSurfaceSession] used to set the preview. Use it to call [ViewfinderSurfaceSession.close]. + */ +suspend fun IPreviewableSource.startPreview( + viewfinderView: ViewfinderView, + surfaceRequest: ViewfinderSurfaceRequest +): ViewfinderSurfaceSession { + val surfaceSession = setPreview(viewfinderView, surfaceRequest) + startPreview() + return surfaceSession }