@@ -19,6 +19,7 @@ import android.content.Context
1919import android.graphics.Bitmap
2020import android.view.Surface
2121import androidx.annotation.IntRange
22+ import io.github.thibaultbee.streampack.core.elements.interfaces.ISnapshotable
2223import io.github.thibaultbee.streampack.core.elements.processing.video.ISurfaceProcessorInternal
2324import io.github.thibaultbee.streampack.core.elements.processing.video.outputs.ISurfaceOutput
2425import io.github.thibaultbee.streampack.core.elements.processing.video.outputs.SurfaceOutput
@@ -45,18 +46,13 @@ import kotlinx.coroutines.launch
4546import kotlinx.coroutines.sync.Mutex
4647import kotlinx.coroutines.sync.withLock
4748import kotlinx.coroutines.withContext
48- import java.io.File
49- import java.io.FileOutputStream
50- import java.io.OutputStream
5149import java.util.concurrent.atomic.AtomicBoolean
52- import kotlin.coroutines.resume
53- import kotlin.coroutines.suspendCoroutine
5450
5551/* *
5652 * The public interface for the video input.
5753 * It provides access to the video source, the video processor, and the streaming state.
5854 */
59- interface IVideoInput {
55+ interface IVideoInput : ISnapshotable {
6056
6157 /* *
6258 * Whether the video input is streaming.
@@ -85,64 +81,6 @@ interface IVideoInput {
8581 * The video processor for adding effects to the video frames.
8682 */
8783 val processor: ISurfaceProcessorInternal
88-
89- /* *
90- * Takes a snapshot of the current video frame.
91- *
92- * The snapshot is returned as a [Bitmap].
93- *
94- * @param rotationDegrees The rotation to apply to the snapshot, in degrees. 0 means no rotation.
95- * @return The snapshot as a [Bitmap].
96- */
97- suspend fun takeSnapshot (@IntRange(from = 0 , to = 359 ) rotationDegrees : Int = 0): Bitmap
98- }
99-
100- /* *
101- * Takes a JPEG snapshot of the current video frame.
102- *
103- * The snapshot is saved to the specified file.
104- *
105- * @param filePathString The path of the file to save the snapshot to.
106- * @param quality The quality of the JPEG, from 0 to 100.
107- * @param rotationDegrees The rotation to apply to the snapshot, in degrees.
108- */
109- suspend fun IVideoInput.takeJpegSnapshot (
110- filePathString : String ,
111- @IntRange(from = 0 , to = 100 ) quality : Int = 100,
112- @IntRange(from = 0 , to = 359 ) rotationDegrees : Int = 0
113- ) = takeJpegSnapshot(FileOutputStream (filePathString), quality, rotationDegrees)
114-
115-
116- /* *
117- * Takes a JPEG snapshot of the current video frame.
118- *
119- * The snapshot is saved to the specified file.
120- *
121- * @param file The file to save the snapshot to.
122- * @param quality The quality of the JPEG, from 0 to 100.
123- * @param rotationDegrees The rotation to apply to the snapshot, in degrees.
124- */
125- suspend fun IVideoInput.takeJpegSnapshot (
126- file : File ,
127- @IntRange(from = 0 , to = 100 ) quality : Int = 100,
128- @IntRange(from = 0 , to = 359 ) rotationDegrees : Int = 0
129- ) = takeJpegSnapshot(FileOutputStream (file), quality, rotationDegrees)
130-
131- /* *
132- * Takes a snapshot of the current video frame.
133- *
134- * The snapshot is saved as a JPEG to the specified output stream.
135- * @param outputStream The output stream to save the snapshot to.
136- * @param quality The quality of the JPEG, from 0 to 100.
137- * @param rotationDegrees The rotation to apply to the snapshot, in degrees.
138- */
139- suspend fun IVideoInput.takeJpegSnapshot (
140- outputStream : OutputStream ,
141- @IntRange(from = 0 , to = 100 ) quality : Int = 100,
142- @IntRange(from = 0 , to = 359 ) rotationDegrees : Int = 0
143- ) {
144- val bitmap = takeSnapshot(rotationDegrees)
145- bitmap.compress(Bitmap .CompressFormat .JPEG , quality, outputStream)
14684}
14785
14886/* *
@@ -436,6 +374,13 @@ internal class VideoInput(
436374 return newSurfaceProcessor
437375 }
438376
377+ /* *
378+ * Takes a snapshot of the current video frame.
379+ *
380+ * It starts the video source if needed.
381+ *
382+ * The snapshot is returned as a [Bitmap].
383+ */
439384 override suspend fun takeSnapshot (
440385 @IntRange(
441386 from = 0 ,
@@ -445,14 +390,13 @@ internal class VideoInput(
445390 if (isReleaseRequested.get()) {
446391 throw IllegalStateException (" Input is released" )
447392 }
448- return startStreamForBlock {
449- suspendCoroutine { continuation ->
450- val listener = processor.snapshot(rotationDegrees)
451- try {
452- val bitmap = listener.get()
453- continuation.resume(bitmap)
454- } catch (e: Exception ) {
455- continuation.resumeWith(Result .failure(e))
393+ return withContext(dispatcherProvider.default) {
394+ sourceMutex.withLock {
395+ val processor = processor as ? ISnapshotable ?
396+ ? : throw IllegalStateException (" Processor is not a snapshotable" )
397+
398+ startStreamForBlockUnsafe {
399+ processor.takeSnapshot(rotationDegrees)
456400 }
457401 }
458402 }
@@ -605,23 +549,19 @@ internal class VideoInput(
605549 *
606550 * If the stream was already running, it will not be stopped after the block.
607551 */
608- private suspend fun <T > startStreamForBlock (block : suspend () -> T ): T {
552+ private suspend fun <T > startStreamForBlockUnsafe (block : suspend () -> T ): T {
609553 if (isReleaseRequested.get()) {
610554 throw IllegalStateException (" Input is released" )
611555 }
612- return withContext(dispatcherProvider.default) {
613- sourceMutex.withLock {
614- val wasStreaming = isStreamingFlow.value
615- if (! wasStreaming) {
616- startStreamUnsafe()
617- }
618- try {
619- block()
620- } finally {
621- if (! wasStreaming) {
622- stopStreamUnsafe()
623- }
624- }
556+ val wasStreaming = isStreamingFlow.value
557+ if (! wasStreaming) {
558+ startStreamUnsafe()
559+ }
560+ return try {
561+ block()
562+ } finally {
563+ if (! wasStreaming) {
564+ stopStreamUnsafe()
625565 }
626566 }
627567 }
0 commit comments