ofVideoPlayer frame freezes after long duration (OS X)

I’ve built a script that runs 4 1920x1080px .h264 videos using ofVideoPlayer. The videos need to play in sync, so they’re tiled horizontally and repeat after 10 minutes. After looping for extended periods (5-8 hours) of time, some videos freeze their frames and do not work unless I restart the video loop on all 4 videos. The timecode of the frozen video continues to increment. Using ofxMemoryUsage I get steady memory readouts. I’m not sure why this is happening, or how to overcome the issue. Any advice?

(Mac OS X 10.10.4, oF 0.8.4, Mac Pro)

#include "ofApp.h"

const int NUM_VIDEOS = 4;
const string PATH_ROOT = "/Users/administrator/Desktop/CLIENT/";
const string VIDEO_PATH_START = "CLIENT_";
const string VIDEO_PATH_END = "_FINAL_1.mp4";
const ofRectangle VIDEO_SIZE = ofRectangle(0, 0, 1920, 1080);
const int ADMIN = 1;

void ofApp::setup() {
    ofSetVerticalSync(true);
    ofSetWindowTitle("Projection Mapped Dome");
    mem.setup();

    for (int i = 0; i < NUM_VIDEOS; i++) {
        string dest = (i == 0) ? "A" : (i == 1) ? "B" : (i == 2) ? "C" : "D";

        ofVideoPlayer vid;
        vid.load(PATH_ROOT + VIDEO_PATH_START + dest + VIDEO_PATH_END);
        vid.setLoopState(OF_LOOP_NONE);
        videos.push_back(vid);
    }

    cout << "VIDEOS: " << videos.size() << endl;

    fbo.allocate(VIDEO_SIZE.width * (NUM_VIDEOS + ADMIN), VIDEO_SIZE.height);

    bPlay = false;
}

void ofApp::update() {
    mem.update();
    
    if (bPlay) {
        for (int i = 0; i < NUM_VIDEOS; i++)
            videos[i].update();
    
        if (videos[0].getIsMovieDone()) {
            // Check the first movie for completion before resetting all the videos to 
            // eliminate out of sync possibility
            reset(); 
        }
    }

    fbo.begin();
    ofClear(255, 255, 255, 0);
    for (int i = 0; i < NUM_VIDEOS + ADMIN; i++) {
        if (i > 0) {
            ofPushMatrix();
            ofTranslate(VIDEO_SIZE.width * i, 0);
            if (bPlay)
                videos[(i - ADMIN)].draw(0, 0);
            ofPopMatrix();
        }
    }
    fbo.end();
}

void ofApp::draw() {
    ofBackground(0);
    fbo.draw(0, 0, 3200, 360);

    stringstream ss;
    ss << "FPS:             " << ofToString(ofGetFrameRate()) << "\n";
    ss << "A POSITION:      " << ofToString(videos[0].getPosition()) << "\n";
    ss << "B POSITION:      " << ofToString(videos[1].getPosition()) << "\n";
    ss << "C POSITION:      " << ofToString(videos[2].getPosition()) << "\n";
    ss << "D POSITION:      " << ofToString(videos[3].getPosition()) << "\n";
    ss << "PLAYING     ( ): " << ((bPlay) ? "TRUE" : "FALSE") << "\n";
    ss << "TEMPLATE    (t): " << ((bTemplate) ? "TRUE" : "FALSE") << "\n";
    ss << "FULLSCREEN  (f)  " << "\n";
    ss << "RESET VIDEO (r)  " << "\n";
    ss << "PROCESS MEMORY:  " << ofToString(mem.getProcessMemory()) << "\n";
    ss << "USED MEMORY:     " << ofToString(mem.getUsedMemory()) << "\n";
    ss << "TOTAL MEMORY:    " << ofToString(mem.getTotalMemory()) << "\n";
    ofDrawBitmapString(ss.str(), 10, 10);
}

void ofApp::keyReleased(int key) {
    if (key == ' ') {
        bPlay = !bPlay;
        (bPlay) ? play() : stop();
    } else if (key == 't') {
        bTemplate = !bTemplate;
    } else if (key == 'f') {
        ofToggleFullscreen();
    } else if (key == 'r') {
        reset();
    }
}

void ofApp::play() {
    cout << "PLAY" << endl;
    videos[0].play(); // Not using a for loop here because it made the freezing video issue worse
    videos[1].play();
    videos[2].play();
    videos[3].play();
}

void ofApp::reset() {
    cout << "RESET" << endl;
    videos[0].stop(); // Not using a for loop here because it made the freezing video issue worse
    videos[1].stop();
    videos[2].stop();
    videos[3].stop();

    videos[0].play();
    videos[1].play();
    videos[2].play();
    videos[3].play();
}

void ofApp::stop() {
    cout << "STOP" << endl;
    videos[0].setPaused(true); // Not using a for loop here because it made the freezing video issue worse
    videos[1].setPaused(true);
    videos[2].setPaused(true);
    videos[3].setPaused(true);
}

I’ve found that getIsMovieDone() is sometimes unreliable - I’ve had the same problem before where I want to trigger something when a movie finishes, but sometime it won’t actually “finish” and just stays frozen at the end. In trying to debug the problem, I’ve noticed that the movie will reach the end and stop playing, but getCurrentFrame() will return a few frames less than getTotalNumFrames(), which is probably why getIsMovieDone never returns true.

I assume this has something to do with diferences between the movie’s frame rate and the GPU’s frame rate. I’m sure somebody here could give you a better answer as to why, but there are two things that have reliably worked for me:

If you have no other program-logic that would stop your movie’s playback, you simply test isPlaying(). If that returns false, it should be the same thing as the movie being “done.” (Make sure to setLoopState(OF_LOOP_NONE) so that the movie does actually stop when it’s finished).

If that’s not an option, you can do something like:

if(videos[0].getCurrentFrame() >= videos[0].getTotalNumFrames()-5){
    reset();
}

Usually you can spare a handful of frames at the end of your video, but obviously that might not be ideal if there is a seamless loop.

Hope this helps!

Thanks for your response, ivaylopg. I’ve made some modifications. Now calling the array elements individually instead of in a for loop (this seems to solve the reset method, trying it in update), and changed the reset to your recommended isPlaying(). OF_LOOP_NONE is already set on the videos.

I still don’t understand why the frame would freeze on the video while the playhead advances. Could it be a bug in the AVFoundation (I believe this is the layer I’m working in) implementation?

Letting the application run overnight to test again.

8 hours later, the video frame still freezes but it recovers on the reset(). I took out the for loops in order to resolve this issue. Thinking of rebuilding the application in Cinder to see if it handles things better.