Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ISourceInfoProvider>

/**
* Mutex for the preview.
* Use it when you have to synchronise access to the preview.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions demos/camera/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ android {

defaultConfig {
applicationId = "io.github.thibaultbee.streampack.sample"

minSdk = 23
}
buildFeatures {
viewBinding = true
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions ui/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import utils.AndroidVersions

plugins {
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
Expand All @@ -8,6 +10,10 @@ description = "UI components for StreamPack."

android {
namespace = "io.github.thibaultbee.streampack.ui"

defaultConfig {
minSdk = 23
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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,
Expand All @@ -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")
Expand All @@ -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
}
}

Expand All @@ -421,8 +417,8 @@ class PreviewView @JvmOverloads constructor(
Logger.d(TAG, "Preview stopped")
}
}
viewfinderSurfaceRequest?.markSurfaceSafeToRelease()
viewfinderSurfaceRequest = null
surfaceRequestSession?.close()
surfaceRequestSession = null
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading