My current OPENRNDR program does the following:
- loads videos and image files from a ‘playlist’ folder and assigns them to Scenes on a timeline
- plays these files sequentially after the project is loaded and I press space bar.
On the surface, it’s current state works perfectly.
Problem arises when I try to load two videos.
When a second video is loaded and played, the console log shows a repeated “error? -11” message.
This only happens when more than one video is loaded and played.
Strange thing is that the error occurs but the visual output is perfect and the program doesn’t crash. For the life of me, I couldn’t find anywhere what “error? -11” means.
I’m not asking for a fix or anything, but guidance on where to look/research to fix this. Thanks in advance.
Here’s the relevant code parts:
// File: utils/VideoManager.kt
package utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import models.Scene
import models.VideoState
import org.openrndr.ffmpeg.VideoPlayerFFMPEG
import java.io.File
import java.util.logging.Logger
object VideoManager {
private val logger = Logger.getLogger(VideoManager::class.java.name)
private val loadedVideos = mutableMapOf<File, VideoPlayerFFMPEG>()
private val loadingVideos = mutableSetOf<File>()
suspend fun loadVideo(file: File): VideoPlayerFFMPEG? {
return when {
loadedVideos.containsKey(file) -> {
logger.fine("Video already loaded: ${file.name}")
loadedVideos[file]
}
loadingVideos.contains(file) -> {
logger.fine("Video is currently loading: ${file.name}")
null
}
else -> {
loadingVideos.add(file)
try {
logger.info("Loading video: ${file.name}")
val video = withContext(Dispatchers.IO) {
VideoPlayerFFMPEG.fromFile(file.absolutePath)
}
loadedVideos[file] = video
logger.info("Successfully loaded video: ${file.name}")
video
} catch (e: Exception) {
logger.warning("Failed to load video: ${file.name} - ${e.message}")
null
} finally {
loadingVideos.remove(file)
}
}
}
}
fun releaseVideo(scene: Scene) {
scene.videoFile?.let { file ->
logger.info("Attempting to release video: ${file.name}")
loadedVideos[file]?.let { video ->
stopVideo(scene)
video.dispose()
loadedVideos.remove(file)
logger.info("Successfully released video: ${file.name}")
}
}
}
fun releaseAllVideos(scenes: List<Scene>) {
logger.info("Releasing all videos")
scenes.forEach { scene ->
scene.videoFile?.let { file ->
loadedVideos[file]?.let { video ->
stopVideo(scene)
video.dispose()
logger.info("Released video: ${file.name}")
}
}
}
loadedVideos.clear()
loadingVideos.clear()
logger.info("All videos released")
}
private fun stopVideo(scene: Scene) {
scene.videoPlayer?.let { player ->
logger.info("Stopping video for scene ${scene.number}")
player.pause()
player.seek(0.0)
scene.videoState = VideoState.STOPPED
}
}
}
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.loadFont
import org.openrndr.draw.loadImage
import org.openrndr.draw.ColorBuffer
import org.openrndr.dialogs.openFolderDialog
import kotlinx.coroutines.*
import models.Project
import models.VideoState
import renderers.TimelineRenderer
import utils.*
import java.io.File
import java.util.logging.Logger
import java.util.logging.Level
private val logger = Logger.getLogger("MainApplication")
fun main() = application {
configure {
width = 960
height = 540
title = "ChannelFactory Video Editor"
}
program {
logger.info("Initializing ChannelFactory Video Editor")
val font = loadFont("data/fonts/default.otf", 16.0)
var project: Project? = null
var projectFolder: File? = null
var message: String = "ChannelFactory Video Editor"
var timelineRenderer: TimelineRenderer? = null
val loadedImages = mutableMapOf<File, ColorBuffer>()
fun loadImageIfNeeded(file: File): ColorBuffer? {
return loadedImages[file] ?: run {
try {
logger.info("Loading image: ${file.name}")
val image = loadImage(file)
loadedImages[file] = image
logger.info("Image loaded successfully: ${file.name}")
image
} catch (e: Exception) {
logger.log(Level.WARNING, "Failed to load image: ${file.name}", e)
null
}
}
}
fun reloadProject(projectFolder: File) {
logger.info("Reloading project from folder: ${projectFolder.absolutePath}")
runBlocking {
launch(Dispatchers.Default) {
val loadedProject = loadProject(projectFolder)
if (loadedProject != null) {
project = loadedProject
timelineRenderer = TimelineRenderer(loadedProject.scenes)
message = "Project reloaded successfully!"
loadedImages.clear()
logger.info("Releasing all videos before reloading")
VideoManager.releaseAllVideos(loadedProject.scenes)
logger.info("Preloading videos asynchronously")
loadedProject.scenes.forEach { scene ->
scene.videoFile?.let { file ->
launch(Dispatchers.IO) {
logger.info("Preloading video for scene ${scene.number}: ${file.name}")
VideoManager.loadVideo(file)
}
}
}
logger.info("Project reloaded successfully")
} else {
logger.warning("Failed to reload project. Invalid folder selected.")
message = "Failed to reload project. Please select a valid folder."
}
}
}
}
extend {
val currentTime = project?.let { proj ->
if (proj.playing) System.currentTimeMillis() - proj.startTime else proj.startTime
} ?: 0L
drawer.clear(ColorRGBa.BLACK)
drawer.fill = ColorRGBa.WHITE
drawer.fontMap = font
drawer.text(message, 20.0, 30.0)
drawer.text("Press 'L' to load a project", 20.0, 60.0)
drawer.text("Press 'Space' to play/pause", 20.0, 90.0)
drawer.text("Press 'R' to stop", 20.0, 120.0)
project?.let { proj ->
drawer.text("Total Scenes: ${proj.scenes.size}", 20.0, 150.0)
proj.scenes.forEachIndexed { index, scene ->
val minutes = scene.startTime / 60000
val seconds = (scene.startTime % 60000) / 1000
val firstWords = scene.text.split(" ").take(5).joinToString(" ") + "..."
drawer.text("Scene ${index + 1}: ${minutes}m ${seconds}s - $firstWords", 20.0, 180.0 + index * 20.0)
}
timelineRenderer?.draw(drawer, width.toDouble(), height.toDouble(), mouse.position)
val activeScene = proj.scenes.findLast { it.startTime <= currentTime }
activeScene?.let { scene ->
logger.fine("Processing active scene ${scene.number}")
proj.sceneMediaMap[scene.number]?.forEach { file ->
when (file.extension.lowercase()) {
in listOf("jpg", "jpeg", "png", "bmp") -> {
loadImageIfNeeded(file)?.let { image ->
val imageScale = minOf(width.toDouble() / image.width, height.toDouble() / image.height)
val imageWidth = image.width * imageScale
val imageHeight = image.height * imageScale
val imageX = (width - imageWidth) / 2.0
val imageY = (height - imageHeight) / 2.0
drawer.image(image, imageX, imageY, imageWidth, imageHeight)
}
}
in listOf("mp4", "avi", "mov") -> {
runBlocking {
logger.fine("Processing video file: ${file.name}")
VideoManager.loadVideo(file)?.let { player ->
when (proj.playing) {
true -> {
if (scene.videoState == VideoState.STOPPED) {
logger.info("Starting video from beginning for scene ${scene.number}")
player.seek(0.0)
player.play()
scene.videoState = VideoState.PLAYING
} else if (scene.videoState == VideoState.PAUSED) {
logger.info("Resuming paused video for scene ${scene.number}")
player.play()
scene.videoState = VideoState.PLAYING
}
}
false -> {
if (scene.videoState == VideoState.PLAYING) {
logger.info("Pausing playing video for scene ${scene.number}")
player.pause()
scene.videoState = VideoState.PAUSED
}
}
}
player.draw(drawer, 0.0, 0.0, width.toDouble(), height.toDouble())
}
}
}
}
}
}
// Check if we need to move to the next scene
val nextSceneIndex = proj.scenes.indexOfFirst { it.startTime > currentTime }
if (nextSceneIndex != -1 && nextSceneIndex != proj.currentSceneIndex) {
logger.info("Moving to next scene. Current: ${proj.currentSceneIndex}, Next: $nextSceneIndex")
stopVideo(proj.scenes[proj.currentSceneIndex])
proj.currentSceneIndex = nextSceneIndex
if (proj.playing) {
playVideo(proj.scenes[proj.currentSceneIndex])
}
}
}
}
keyboard.keyDown.listen { key ->
when (key.name) {
"l" -> {
logger.info("Load project key pressed")
openFolderDialog { folder ->
folder?.let {
logger.info("Project folder selected: ${it.absolutePath}")
projectFolder = it
reloadProject(it)
}
}
}
"space" -> {
logger.info("Play/Pause key pressed")
project?.let { proj ->
proj.playing = !proj.playing
if (proj.playing) {
logger.info("Starting playback")
proj.startTime = System.currentTimeMillis() - (proj.startTime.takeIf { it > 0 } ?: 0L)
playVideo(proj.scenes[proj.currentSceneIndex])
} else {
logger.info("Pausing playback")
pauseVideo(proj.scenes[proj.currentSceneIndex])
}
}
}
"r" -> {
logger.info("Reload project key pressed")
projectFolder?.let {
reloadProject(it)
} ?: run {
logger.warning("No project loaded to reload")
message = "No project loaded to reload."
}
}
}
}
ended.listen {
logger.info("Application ending")
project?.let {
logger.info("Releasing all videos and resources")
VideoManager.releaseAllVideos(it.scenes)
loadedImages.values.forEach { it.destroy() }
loadedImages.clear()
}
}
}
}
Relevant part of the Log:
out. 02, 2024 11:00:27 AM MainKt$main$1$2$reloadProject$1$1 invokeSuspend
INFORMAÇÕES: Preloading videos asynchronously
out. 02, 2024 11:00:27 AM MainKt$main$1$2$reloadProject$1$1 invokeSuspend
INFORMAÇÕES: Project reloaded successfully
out. 02, 2024 11:00:28 AM MainKt$main$1$2$2 invoke
INFORMAÇÕES: Play/Pause key pressed
out. 02, 2024 11:00:28 AM MainKt$main$1$2$2 invoke
INFORMAÇÕES: Starting playback
out. 02, 2024 11:00:28 AM utils.VideoManager loadVideo
INFORMAÇÕES: Loading video: 01.mp4
out. 02, 2024 11:00:30 AM utils.VideoManager loadVideo
INFORMAÇÕES: Successfully loaded video: 01.mp4
out. 02, 2024 11:00:30 AM MainKt$main$1$2$1$1$2$1$2 invokeSuspend
INFORMAÇÕES: Starting video from beginning for scene 1
out. 02, 2024 11:00:31 AM MainKt$main$1$2$1 invoke
INFORMAÇÕES: Moving to next scene. Current: 0, Next: 1
out. 02, 2024 11:00:35 AM utils.VideoManager loadVideo
INFORMAÇÕES: Loading video: 02.mp4
out. 02, 2024 11:00:35 AM utils.VideoManager loadVideo
INFORMAÇÕES: Successfully loaded video: 02.mp4
out. 02, 2024 11:00:35 AM MainKt$main$1$2$1$1$2$1$2 invokeSuspend
INFORMAÇÕES: Starting video from beginning for scene 2
out. 02, 2024 11:00:35 AM MainKt$main$1$2$1 invoke
INFORMAÇÕES: Moving to next scene. Current: 1, Next: 2
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11
error? -11