Skip to content

Commit cec23c1

Browse files
authored
Merge pull request #1157 from tunjid/tj/media-logging
Log media errors
2 parents c6bc4cf + 2494c6d commit cec23c1

File tree

3 files changed

+79
-62
lines changed

3 files changed

+79
-62
lines changed

ui/media/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ kotlin {
4141
commonMain {
4242
dependencies {
4343
implementation(project(":data:files"))
44+
implementation(project(":data:logging"))
4445
implementation(project(":data:models"))
4546
implementation(project(":ui:core"))
4647

ui/media/src/desktopMain/kotlin/com/tunjid/heron/media/video/mac/AVFoundationPlayerState.kt

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import androidx.compose.ui.graphics.asComposeImageBitmap
2323
import androidx.compose.ui.layout.ContentScale
2424
import androidx.compose.ui.unit.IntSize
2525
import com.sun.jna.Pointer
26+
import com.tunjid.heron.data.logging.LogPriority
27+
import com.tunjid.heron.data.logging.logcat
28+
import com.tunjid.heron.data.logging.loggableText
2629
import com.tunjid.heron.media.video.PlayerStatus
2730
import com.tunjid.heron.media.video.VideoPlayerState
2831
import com.tunjid.heron.ui.shapes.RoundedPolygonShape
@@ -98,8 +101,7 @@ internal class AVFoundationPlayerState(
98101

99102
// Frame rendering state
100103
internal var currentFrame by mutableStateOf<ImageBitmap?>(null)
101-
private var skiaBitmapWidth: Int = 0
102-
private var skiaBitmapHeight: Int = 0
104+
private var currentFrameSize = IntSize.Zero
103105
private var skiaBitmapA: Bitmap? = null
104106
private var skiaBitmapB: Bitmap? = null
105107
private var nextSkiaBitmapA: Boolean = true
@@ -236,64 +238,71 @@ internal class AVFoundationPlayerState(
236238
private suspend fun updateFrameAsync() {
237239
val ptr = playerPointer ?: return
238240

239-
val width = AVFoundationVideoPlayer.getFrameWidth(ptr)
240-
val height = AVFoundationVideoPlayer.getFrameHeight(ptr)
241-
if (width <= 0 || height <= 0) return
242-
243-
val framePtr = AVFoundationVideoPlayer.getLatestFrame(ptr) ?: return
244-
245-
val pixelCount = width * height
246-
val frameSizeBytes = pixelCount.toLong() * 4L
247-
248-
withContext(serialDispatcher) {
249-
val srcBuf = framePtr.getByteBuffer(0, frameSizeBytes)
250-
251-
if (skiaBitmapA == null || skiaBitmapWidth != width || skiaBitmapHeight != height) {
252-
skiaBitmapA?.close()
253-
skiaBitmapB?.close()
254-
255-
val imageInfo = ImageInfo(
256-
width = width,
257-
height = height,
258-
colorType = ColorType.BGRA_8888,
259-
alphaType = ColorAlphaType.OPAQUE,
260-
)
261-
skiaBitmapA = Bitmap().apply { allocPixels(imageInfo) }
262-
skiaBitmapB = Bitmap().apply { allocPixels(imageInfo) }
263-
skiaBitmapWidth = width
264-
skiaBitmapHeight = height
265-
nextSkiaBitmapA = true
266-
}
267-
268-
val targetBitmap =
269-
if (nextSkiaBitmapA) requireNotNull(skiaBitmapA)
270-
else requireNotNull(skiaBitmapB)
271-
272-
nextSkiaBitmapA = !nextSkiaBitmapA
273-
274-
val pixmap = targetBitmap.peekPixels() ?: return@withContext
275-
val pixelsAddr = pixmap.addr
276-
if (pixelsAddr == 0L) return@withContext
277-
278-
srcBuf.rewind()
279-
val destRowBytes = pixmap.rowBytes
280-
val destSizeBytes = destRowBytes.toLong() * height.toLong()
281-
val destBuf = Pointer(pixelsAddr).getByteBuffer(0, destSizeBytes)
282-
copyBgraFrame(
283-
src = srcBuf,
284-
dst = destBuf,
285-
width = width,
286-
height = height,
287-
dstRowBytes = destRowBytes,
241+
try {
242+
val latestFrameSize = IntSize(
243+
width = AVFoundationVideoPlayer.getFrameWidth(ptr),
244+
height = AVFoundationVideoPlayer.getFrameHeight(ptr),
288245
)
246+
if (latestFrameSize.width <= 0 || latestFrameSize.height <= 0) return
247+
248+
val framePtr = AVFoundationVideoPlayer.getLatestFrame(ptr) ?: return
249+
250+
val pixelCount = latestFrameSize.width * latestFrameSize.height
251+
val frameSizeBytes = pixelCount.toLong() * 4L
252+
253+
withContext(serialDispatcher) {
254+
val srcBuf = framePtr.getByteBuffer(0, frameSizeBytes)
255+
256+
if (skiaBitmapA == null || currentFrameSize != latestFrameSize) {
257+
skiaBitmapA?.close()
258+
skiaBitmapB?.close()
259+
260+
val imageInfo = ImageInfo(
261+
width = latestFrameSize.width,
262+
height = latestFrameSize.height,
263+
colorType = ColorType.BGRA_8888,
264+
alphaType = ColorAlphaType.OPAQUE,
265+
)
266+
skiaBitmapA = Bitmap().apply { allocPixels(imageInfo) }
267+
skiaBitmapB = Bitmap().apply { allocPixels(imageInfo) }
268+
currentFrameSize = latestFrameSize
269+
nextSkiaBitmapA = true
270+
}
271+
272+
val targetBitmap =
273+
if (nextSkiaBitmapA) requireNotNull(skiaBitmapA)
274+
else requireNotNull(skiaBitmapB)
275+
276+
nextSkiaBitmapA = !nextSkiaBitmapA
277+
278+
val pixmap = targetBitmap.peekPixels() ?: return@withContext
279+
val pixelsAddr = pixmap.addr
280+
if (pixelsAddr == 0L) return@withContext
281+
282+
srcBuf.rewind()
283+
val destRowBytes = pixmap.rowBytes
284+
val destSizeBytes = destRowBytes.toLong() * latestFrameSize.height.toLong()
285+
val destBuf = Pointer(pixelsAddr).getByteBuffer(0, destSizeBytes)
286+
copyBgraFrame(
287+
src = srcBuf,
288+
dst = destBuf,
289+
width = latestFrameSize.width,
290+
height = latestFrameSize.height,
291+
dstRowBytes = destRowBytes,
292+
)
289293

290-
currentFrame = targetBitmap.asComposeImageBitmap()
291-
}
294+
currentFrame = targetBitmap.asComposeImageBitmap()
295+
}
292296

293-
lastFrameUpdateTime = System.currentTimeMillis()
297+
lastFrameUpdateTime = System.currentTimeMillis()
294298

295-
if (!hasRenderedFirstFrame) {
296-
hasRenderedFirstFrame = true
299+
if (!hasRenderedFirstFrame) {
300+
hasRenderedFirstFrame = true
301+
}
302+
} catch (e: Exception) {
303+
logcat(LogPriority.ERROR) {
304+
"Video frame extraction failed: ${e.loggableText()}"
305+
}
297306
}
298307
}
299308

@@ -318,8 +327,7 @@ internal class AVFoundationPlayerState(
318327
skiaBitmapB?.close()
319328
skiaBitmapA = null
320329
skiaBitmapB = null
321-
skiaBitmapWidth = 0
322-
skiaBitmapHeight = 0
330+
currentFrameSize = IntSize.Zero
323331
nextSkiaBitmapA = true
324332

325333
pointerToDispose?.let(AVFoundationVideoPlayer::disposeVideoPlayer)

ui/media/src/nonAndroidMain/kotlin/com/tunjid/heron/images/AnimatedSkiaImage.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import androidx.compose.runtime.mutableIntStateOf
2222
import androidx.compose.runtime.setValue
2323
import coil3.Canvas
2424
import coil3.Image
25+
import com.tunjid.heron.data.logging.LogPriority
26+
import com.tunjid.heron.data.logging.logcat
27+
import com.tunjid.heron.data.logging.loggableText
2528
import org.jetbrains.skia.AnimationFrameInfo
2629
import org.jetbrains.skia.Bitmap
2730
import org.jetbrains.skia.Codec
@@ -90,10 +93,15 @@ internal class AnimatedSkiaImage(
9093

9194
internal fun decodeFrame(frameIndex: Int): org.jetbrains.skia.Image? {
9295
if (tempBitmap.isClosed) return null
93-
codec.readPixels(tempBitmap, frameIndex)
94-
return org.jetbrains.skia.Image.makeFromBitmap(
95-
tempBitmap,
96-
)
96+
return try {
97+
codec.readPixels(tempBitmap, frameIndex)
98+
org.jetbrains.skia.Image.makeFromBitmap(tempBitmap)
99+
} catch (e: Exception) {
100+
logcat(LogPriority.ERROR) {
101+
"Gif frame decode failed: ${e.loggableText()}"
102+
}
103+
null
104+
}
97105
}
98106

99107
internal fun closeTempBitmap() {

0 commit comments

Comments
 (0)