Hacked Movie EoS Event Library

Processing’s Video library doesn’t offer any means to flag a Movie stream has ended.

A long time ago I’ve posted a hack solution that used to work back then:

But more recent changes to the library made that hackish workaround to stop working.

This time around I’ve decided to create a HackedMovie subclass which extends the Movie class:

It features a new boolean field ended which is set to true when a Movie stream has finished.

Also a new callback eosEvent(), in addition to movieEvent(), which is invoked right after a Movie stream has finished.

More EoS listeners can be added via new method HackedMovie::addEOSListener().

Just add the “HackedMovie.java” file to your sketch and import it like this:
import processing.video.HackedMovie;

In short, HackedMovie subclass is used as a replacement to the Movie class.

P.S.: That “.java” file relies on the original video library, so you have to install it so subclass HackedMovie can work.

Important update (v2.1.0):
Callback eosEvent() requires now a Movie or HackedMovie parameter, just like movieEvent():

“HackedMovie.java”:

/**
 * Hacked Movie EoS Event (v2.1.0)
 * by GoToLoop (2022-Oct-20)
 *
 * Gist.GitHub.com/GoToLoop/67023ba3e9ba1d0301c40f7c030d176a
 *
 * Discourse.Processing.org/t/hacked-movie-eos-event-library/39375
 * Forum.Processing.org/two/discussion/14990/movie-begin-and-end-events#Item_1
 */

package processing.video;

import processing.core.PApplet;
import java.lang.reflect.Method;

import org.freedesktop.gstreamer.GstObject;
import static org.freedesktop.gstreamer.Bus.EOS;

public class HackedMovie extends Movie {
  public Method eosEventMethod;
  public boolean ended;

  public HackedMovie(final PApplet p, final String filename) {
    super(p, filename);
    addEOSListener(new MyEOS()).lookUpAndActivateEOSEventCallback();
  }

  public HackedMovie addEOSListener(final EOS eos) {
    playbin.getBus().connect(eos);
    return this;
  }

  public HackedMovie lookUpAndActivateEOSEventCallback() {
    final Class<?> p = eventHandler.getClass();

    try {
      eosEventMethod = p.getMethod("eosEvent", Movie.class);
    }
    catch (final NoSuchMethodException e) {
    }

    try {
      eosEventMethod = p.getMethod("eosEvent", HackedMovie.class);
    }
    catch (final NoSuchMethodException e) {
    }

    return this;
  }

  @Override public void setEventHandlerObject(final Object o) {
    super.setEventHandlerObject(o);

    final Class<?> p = o.getClass();

    try {
      movieEventMethod = p.getMethod("movieEvent", HackedMovie.class);
    }
    catch (final NoSuchMethodException e) {
    }
  }

  @Override public void play() {
    super.play();
    ended = false;
  }

  class MyEOS implements EOS {
    @Override public void endOfStream(final GstObject _) {
      ended = true;

      if (eosEventMethod != null)  try {
        eosEventMethod.invoke(eventHandler, HackedMovie.this);
      }
      catch (final ReflectiveOperationException e) {
      }
    }
  }
}
4 Likes

You know you can just use movie.playbin.getBus() already, right? That field is public processing-video/Movie.java at master · processing/processing-video · GitHub

Which also means you can do things like change the movie file, etc.

2 Likes

It’s not “already”! AFAIK, it’s always been so. Field playbin has always been public.

BtW, that’s exactly what I’ve used in my addEOSListener() method:

public HackedMovie addEOSListener(final EOS eos) {
  playbin.getBus().connect(eos);
  return this;
}

The idea behind this Processing video library, which includes classes such as Movie & Capture, is to be a simplified wrapper for the GStreamer framework library:

Seems like you’re the lead maintainer of the repo above, and probably wrote this:

Please note, this is not an easy-to-use multimedia framework for beginners. It currently requires people to know the Java language and be familiar with the GStreamer framework (and possibly be prepared to apply things from tutorials on GStreamer programming in other languages to the Java bindings).

So I had to study how the Movie class was creating its own end-of-stream listener via method Bus::conect() and peruse the GStreamer source in order to create my HackedMovie subclass, which relies on the playbin public field as well.

We shouldn’t expect Processing’s target public to do all that by their own just to add such basic end-of-stream listener to the Movie class.

This is an example of a sketch using my HackedMovie subclass in place of the original Movie class:

/**
 * Hacked Movie EoS Event (v2.1.0)
 * by GoToLoop (2022-Oct-20)
 *
 * Gist.GitHub.com/GoToLoop/67023ba3e9ba1d0301c40f7c030d176a
 *
 * Discourse.Processing.org/t/hacked-movie-eos-event-library/39375
 * Forum.Processing.org/two/discussion/14990/movie-begin-and-end-events#Item_1
 */

import processing.video.HackedMovie;
HackedMovie vid;

static final String FILENAME = "transit.mov";

void setup() {
  frameRate(30);
  textSize(050);
  textAlign(CENTER, BASELINE);
  fill(#FFFF00);

  vid = new HackedMovie(this, FILENAME);
  vid.play();

  while (vid.width == 0 | vid.height == 0)  delay(5);
  surface.setSize(vid.width, vid.height);
}

void draw() {
  if (!vid.ended)  background(vid);

  else {
    background((color) random(#000000));
    text("Playback has finished!", width >> 1, height >> 1);
  }
}

void movieEvent(final HackedMovie m) {
  m.read();
}

void eosEvent(final HackedMovie m) {
  frameRate(1);
  println(m);
}
1 Like

I’m well aware of what Processing Video library is and how it uses our code and code originally from PraxisLIVE. Unfortunately, it’s missing some quite obvious functionality from PlayBin, like changing the playing file.

Many parts of the GStreamer wrapper are complicated (yes, I know that text), but the PlayBin side is not that complicated, particularly with addition of lambdas in Processing 4. Still, you have to be really careful with threading using the Bus though, so you’re not simplifying that. What’s wrong with querying whether the Movie is playing anyway btw?

1 Like

A pity I’m still on P3, which even it’s based on Java 8 it surprisingly lacks lambdas!

On P3, in order to invoke method Bus::connect(), I’d need to import both class GstObject & interface Bus.EOS in order to construct its required parameter.

On a P4 lambda I could omit both datatypes b/c interface Bus.EOS fits the definition of a @FunctionalInterface.

I’m just adding a very useful EOS functionality to the Movie class, got nothing to do w/ threading.

Adding an eosEvent() callback plus an ended public boolean field is the very definition of simplification!

A Movie can be in a paused state instead of playing.

Also there’s a repeat state, which we’ll make us miss the EOS event if we’re relying on checking the playing state.

If we have a list of Movie objects to play() 1 after the other, it’s very convenient to have an EOS event callback.

Of course it’s got everything to do with threading. You’ve just introduced one more thread into the user’s sketch. The methods draw(), movieEvent() and eosEvent() are all called on different threads, the latter two in contexts with implications.

I’ve done a lot of work on beginner APIs and teaching non-programmers. Concurrency is hard! If they can understand the implications of that, they’re past the stage they can write a callback. If you want to make Video more beginner friendly, I’d suggest queuing and executing both movieEvent() and eosEvent() in the animation thread in a pre-draw handler.

Polling a boolean flag from draw() is much easier, but you can already get the required logic for that. If you actually need an EOS on a repeating video, you could just manually repeat.

It’s also very convenient to use one Movie object and access the PlayBin to change the file it’s playing - movie.playbin.setInputFile() or movie.playbin.setURI().

This hackish subclass is about adding a simple quick & easy-to-use feature in addition to the already provided movieEvent().

Therefore eosEvent() does indeed have the same common limitations as any pre-defined callbacks from any other Processing libraries such as serialEvent(), disconnectEvent(), etc.

Adding event queues and then force their dequeues to happen within the sketch’s “Animation” Thread (like keyPressed(), mousePressed(), etc.) via pre() or post() is beyond the scope of not only my hack subclass but also for most Processing 3rd-party wrapper libraries that deal w/ hardware bindings.

That’d be a really nice addition for the devs to implement in the Movie class.

But considering Movie is a subclass of PImage I wonder if changing files w/ different dimensions would go smoothly w/ just setInputFile() alone.

Anyways, in order to know when is the time to invoke methods setInputFile() or setURI() to swap the current playing video after finished, we still need to catch the end-of-stream event.

1 Like

Yes, I’ve pointed out before that I consider this to probably be a general failing in the Processing ecosystem. It’s rarely necessary to expose this problem to the end user in a system designed for beginners. When it’s with a library I wrote the threading and memory management for, then yes, I’ll call it out!

You quoted - “Please note, this is not an easy-to-use multimedia framework for beginners. It currently requires people to know the Java language and be familiar with the GStreamer framework” but seem to think this doesn’t apply to understanding the threading context.

Like I said, pass the event into the animation thread, or provide more ways to poll the playbin from draw(), if the existing movie class doesn’t have the required flags.

Yes, it looks like callback registers the new width and height correctly, but perhaps doesn’t pass it up to the superclass. Possibly a mistake that can be rectified - it is handled correctly in PraxisLIVE from where most of the existing callback code came from.

The fact that Movie is an image, rather than providing a stream of images is a problem with the Video library API in my opinion. Creates a whole bunch of issues, including this.

No you don’t, just check if it’s playing in draw() and move on to the next.

2 Likes