4848import com .jme3 .util .BufferUtils ;
4949import java .io .File ;
5050import java .nio .ByteBuffer ;
51+ import java .util .HashMap ;
5152import java .util .List ;
53+ import java .util .Map ;
5254import java .util .concurrent .*;
5355import java .util .logging .Level ;
5456import java .util .logging .Logger ;
@@ -221,28 +223,88 @@ public WorkItem(int width, int height) {
221223 }
222224 }
223225
226+ private class ResolutionWorker {
227+ final int width ;
228+ final int height ;
229+ final LinkedBlockingQueue <WorkItem > freeItems ;
230+ final LinkedBlockingQueue <WorkItem > usedItems ;
231+ MjpegFileWriter writer ;
232+ File file ;
233+
234+ ResolutionWorker (int width , int height , File file ) {
235+ this .width = width ;
236+ this .height = height ;
237+ this .file = file ;
238+ this .freeItems = new LinkedBlockingQueue <>();
239+ this .usedItems = new LinkedBlockingQueue <>();
240+ for (int i = 0 ; i < numCpus ; i ++) {
241+ freeItems .add (new WorkItem (width , height ));
242+ }
243+ }
244+
245+ boolean isFullyDrained () {
246+ return freeItems .size () >= numCpus && usedItems .isEmpty ();
247+ }
248+
249+ void closeWriter () {
250+ if (writer != null ) {
251+ try {
252+ writer .finishAVI ();
253+ Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .INFO ,
254+ "Recording saved to: {0}" , file .getAbsolutePath ());
255+ } catch (Exception ex ) {
256+ Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .SEVERE , "Error closing video" , ex );
257+ }
258+ writer = null ;
259+ }
260+ }
261+ }
262+
224263 private class VideoProcessor implements SceneProcessor {
225264
226265 private Camera camera ;
227266 private int width ;
228267 private int height ;
229268 private RenderManager renderManager ;
230269 private boolean isInitialized = false ;
231- private LinkedBlockingQueue <WorkItem > freeItems ;
232- private LinkedBlockingQueue <WorkItem > usedItems = new LinkedBlockingQueue <>();
233- private MjpegFileWriter writer ;
270+ private ResolutionWorker currentWorker ;
271+ private Map <String , ResolutionWorker > workers = new HashMap <>();
234272 private boolean fastMode = true ;
235- private boolean reshapePending = false ;
236- private int newWidth ;
237- private int newHeight ;
273+
274+ private String getResolutionKey (int w , int h ) {
275+ return w + "x" + h ;
276+ }
277+
278+ private ResolutionWorker getWorker (int w , int h ) {
279+ String key = getResolutionKey (w , h );
280+ ResolutionWorker worker = workers .get (key );
281+ if (worker == null ) {
282+ // Generate filename for this resolution
283+ File workerFile ;
284+ if (file == null ) {
285+ String filename = System .getProperty ("user.home" ) + File .separator + "jMonkey-" + System .currentTimeMillis () / 1000 + ".avi" ;
286+ workerFile = new File (filename );
287+ } else {
288+ String originalPath = file .getAbsolutePath ();
289+ int dotIndex = originalPath .lastIndexOf ('.' );
290+ String basePath = dotIndex > 0 ? originalPath .substring (0 , dotIndex ) : originalPath ;
291+ String extension = dotIndex > 0 ? originalPath .substring (dotIndex ) : ".avi" ;
292+ workerFile = new File (basePath + "-" + w + "x" + h + "-" + (System .currentTimeMillis () / 1000 ) + extension );
293+ }
294+ worker = new ResolutionWorker (w , h , workerFile );
295+ workers .put (key , worker );
296+ }
297+ return worker ;
298+ }
238299
239300 public void addImage (Renderer renderer , FrameBuffer out ) {
240- if (freeItems == null || reshapePending ) {
301+ final ResolutionWorker worker = currentWorker ;
302+ if (worker == null ) {
241303 return ;
242304 }
243305 try {
244- final WorkItem item = freeItems .take ();
245- usedItems .add (item );
306+ final WorkItem item = worker . freeItems .take ();
307+ worker . usedItems .add (item );
246308 item .buffer .clear ();
247309 renderer .readFrameBufferWithFormat (out , item .buffer , Image .Format .BGRA8 );
248310 executor .submit (new Callable <Void >() {
@@ -253,14 +315,14 @@ public Void call() throws Exception {
253315 item .data = item .buffer .array ();
254316 } else {
255317 AndroidScreenshots .convertScreenShot (item .buffer , item .image );
256- item .data = writer .writeImageToBytes (item .image , quality );
318+ item .data = worker . writer .writeImageToBytes (item .image , quality );
257319 }
258- while (usedItems .peek () != item ) {
320+ while (worker . usedItems .peek () != item ) {
259321 Thread .sleep (1 );
260322 }
261- writer .addImage (item .data );
262- usedItems .poll ();
263- freeItems .add (item );
323+ worker . writer .addImage (item .data );
324+ worker . usedItems .poll ();
325+ worker . freeItems .add (item );
264326 return null ;
265327 }
266328 });
@@ -277,12 +339,7 @@ public void initialize(RenderManager rm, ViewPort viewPort) {
277339 this .height = camera .getHeight ();
278340 this .renderManager = rm ;
279341 this .isInitialized = true ;
280- if (freeItems == null ) {
281- freeItems = new LinkedBlockingQueue <WorkItem >();
282- for (int i = 0 ; i < numCpus ; i ++) {
283- freeItems .add (new WorkItem (width , height ));
284- }
285- }
342+ this .currentWorker = getWorker (width , height );
286343 }
287344
288345 @ Override
@@ -291,10 +348,9 @@ public void reshape(ViewPort vp, int w, int h) {
291348 return ;
292349 }
293350
294- // Mark that reshape is pending and store new dimensions
295- this .newWidth = w ;
296- this .newHeight = h ;
297- this .reshapePending = true ;
351+ this .width = w ;
352+ this .height = h ;
353+ this .currentWorker = getWorker (w , h );
298354 }
299355
300356 @ Override
@@ -304,44 +360,20 @@ public boolean isInitialized() {
304360
305361 @ Override
306362 public void preFrame (float tpf ) {
307- // Handle pending reshape if all work items are available
308- if (reshapePending && freeItems != null && freeItems .size () >= numCpus ) {
309- // All work items are free, safe to reshape
310- this .width = newWidth ;
311- this .height = newHeight ;
312- this .reshapePending = false ;
313-
314- // Close the current writer and generate new filename for resized video
315- if (writer != null ) {
316- try {
317- writer .finishAVI ();
318- Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .INFO ,
319- "Window resized from {0}x{1} to {2}x{3}. Previous recording saved to: {4}" ,
320- new Object []{writer .width , writer .height , width , height , file .getAbsolutePath ()});
321- } catch (Exception ex ) {
322- Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .SEVERE , "Error closing video on reshape" , ex );
323- }
324- writer = null ;
325-
326- // Generate a new filename for the resized video
327- String originalPath = file .getAbsolutePath ();
328- int dotIndex = originalPath .lastIndexOf ('.' );
329- String basePath = dotIndex > 0 ? originalPath .substring (0 , dotIndex ) : originalPath ;
330- String extension = dotIndex > 0 ? originalPath .substring (dotIndex ) : ".avi" ;
331- file = new File (basePath + "-" + (System .currentTimeMillis () / 1000 ) + extension );
363+ // Evict old workers that are fully drained
364+ workers .entrySet ().removeIf (entry -> {
365+ ResolutionWorker worker = entry .getValue ();
366+ if (worker != currentWorker && worker .isFullyDrained ()) {
367+ worker .closeWriter ();
368+ return true ;
332369 }
333-
334- // Recreate work items with new dimensions
335- freeItems .clear ();
336- usedItems .clear ();
337- for (int i = 0 ; i < numCpus ; i ++) {
338- freeItems .add (new WorkItem (width , height ));
339- }
340- }
370+ return false ;
371+ });
341372
342- if (null == writer ) {
373+ // Ensure current worker has a writer
374+ if (currentWorker != null && currentWorker .writer == null ) {
343375 try {
344- writer = new MjpegFileWriter (file , width , height , framerate );
376+ currentWorker . writer = new MjpegFileWriter (currentWorker . file , currentWorker . width , currentWorker . height , framerate );
345377 } catch (Exception ex ) {
346378 Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .SEVERE , "Error creating file writer: {0}" , ex );
347379 }
@@ -362,16 +394,19 @@ public void postFrame(FrameBuffer out) {
362394 public void cleanup () {
363395 logger .log (Level .INFO , "cleanup in VideoProcessor" );
364396 logger .log (Level .INFO , "VideoProcessor numFrames: {0}" , numFrames );
365- try {
366- while (freeItems .size () < numCpus ) {
367- Thread .sleep (10 );
397+ // Close all workers
398+ for (ResolutionWorker worker : workers .values ()) {
399+ try {
400+ while (!worker .isFullyDrained ()) {
401+ Thread .sleep (10 );
402+ }
403+ logger .log (Level .INFO , "finishAVI in VideoProcessor for {0}x{1}" , new Object []{worker .width , worker .height });
404+ worker .closeWriter ();
405+ } catch (Exception ex ) {
406+ Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .SEVERE , "Error closing video: {0}" , ex );
368407 }
369- logger .log (Level .INFO , "finishAVI in VideoProcessor" );
370- writer .finishAVI ();
371- } catch (Exception ex ) {
372- Logger .getLogger (VideoRecorderAppState .class .getName ()).log (Level .SEVERE , "Error closing video: {0}" , ex );
373408 }
374- writer = null ;
409+ workers . clear () ;
375410 }
376411
377412 @ Override
0 commit comments