root/src/apps/mediaplayer/media_node_framework/PlaybackManager.cpp
// PlaybackManager.cpp


#include <algorithm>
#include <stdio.h>

#include <Message.h>
#include <OS.h>
#include <Window.h>

#include "EventQueue.h"
#include "MessageEvent.h"
#include "PlaybackListener.h"

#include "PlaybackManager.h"


using namespace std;


//#define TRACE_NODE_MANAGER
#ifdef TRACE_NODE_MANAGER
#       define TRACE(x...)      printf(x)
#       define ERROR(x...)      fprintf(stderr, x)
#else
#       define TRACE(x...)
#       define ERROR(x...)      fprintf(stderr, x)
#endif


void
tdebug(const char* str)
{
        TRACE("[%lx, %lld] ", find_thread(NULL), system_time());
        TRACE(str);
}


#define SUPPORT_SPEED_CHANGES 0


struct PlaybackManager::PlayingState {
        int64           start_frame;
        int64           end_frame;
        int64           frame_count;
        int64           first_visible_frame;
        int64           last_visible_frame;
        int64           max_frame_count;
        int32           play_mode;
        int32           loop_mode;
        bool            looping_enabled;
        bool            is_seek_request;
        int64           current_frame;                  // Playlist frame
        int64           range_index;                    // playing range index of current_frame
        int64           activation_frame;               // absolute video frame

        PlayingState()
        {
        }

        PlayingState(const PlayingState& other)
                :
                start_frame(other.start_frame),
                end_frame(other.end_frame),
                frame_count(other.frame_count),
                first_visible_frame(other.first_visible_frame),
                last_visible_frame(other.last_visible_frame),
                max_frame_count(other.max_frame_count),
                play_mode(other.play_mode),
                loop_mode(other.loop_mode),
                looping_enabled(other.looping_enabled),
                is_seek_request(false),
                current_frame(other.current_frame),
                range_index(other.range_index),
                activation_frame(other.activation_frame)
        {
        }
};


#if SUPPORT_SPEED_CHANGES
struct PlaybackManager::SpeedInfo {
        int64           activation_frame;               // absolute video frame
        bigtime_t       activation_time;                // performance time
        float           speed;                                  // speed to be used for calculations,
                                                                                // is 1.0 if not playing
        float           set_speed;                              // speed set by the user
};
#endif


// #pragma mark - PlaybackManager


PlaybackManager::PlaybackManager()
        :
        BLooper("playback manager"),
        fStates(10),
        fSpeeds(10),
        fCurrentAudioTime(0),
        fCurrentVideoTime(0),
        fPerformanceTime(0),
        fPerformanceTimeEvent(NULL),
        fFrameRate(1.0),
        fStopPlayingFrame(-1),
        fListeners(),
        fNoAudio(false)
{
        Run();
}


PlaybackManager::~PlaybackManager()
{
        Cleanup();
}


void
PlaybackManager::Init(float frameRate, bool initPerformanceTimes,
        int32 loopingMode, bool loopingEnabled, float playbackSpeed,
        int32 playMode, int32 currentFrame)
{
        // cleanup first
        Cleanup();

        // set the new frame rate
        fFrameRate = frameRate;
        if (initPerformanceTimes) {
                fCurrentAudioTime = 0;
                fCurrentVideoTime = 0;
                fPerformanceTime = 0;
        }
        fStopPlayingFrame = -1;

#if SUPPORT_SPEED_CHANGES
        // set up the initial speed
        SpeedInfo* speed = new SpeedInfo;
        speed->activation_frame = 0;
        speed->activation_time = 0;
        speed->speed = playbackSpeed;
        speed->set_speed = playbackSpeed;
        _PushSpeedInfo(speed);
#endif

        // set up the initial state
        PlayingState* state = new PlayingState;
        state->frame_count = Duration();
        state->start_frame = 0;
        state->end_frame = 0;
        state->first_visible_frame = 0;
        state->last_visible_frame = 0;
        state->max_frame_count = state->frame_count;

        state->play_mode = MODE_PLAYING_PAUSED_FORWARD;
        state->loop_mode = loopingMode;
        state->looping_enabled = loopingEnabled;
        state->is_seek_request = false;
        state->current_frame = currentFrame;
        state->activation_frame = 0;
        fStates.AddItem(state);

        TRACE("initial state pushed: state count: %ld\n", fStates.CountItems());

        // notify the listeners
        NotifyPlayModeChanged(PlayMode());
        NotifyLoopModeChanged(LoopMode());
        NotifyLoopingEnabledChanged(IsLoopingEnabled());
        NotifyVideoBoundsChanged(VideoBounds());
        NotifyFPSChanged(FramesPerSecond());
        NotifySpeedChanged(Speed());
        NotifyCurrentFrameChanged(CurrentFrame());
        SetPlayMode(playMode);
}


void
PlaybackManager::Cleanup()
{
        if (EventQueue::Default().RemoveEvent(fPerformanceTimeEvent))
                delete fPerformanceTimeEvent;
        fPerformanceTimeEvent = NULL;
        // delete states
        for (int32 i = 0; PlayingState* state = _StateAt(i); i++)
                delete state;
        fStates.MakeEmpty();

#if SUPPORT_SPEED_CHANGES
        // delete speed infos
        for (int32 i = 0; SpeedInfo* speed = _SpeedInfoAt(i); i++)
                delete speed;
        fSpeeds.MakeEmpty();
#endif
}


void
PlaybackManager::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_EVENT:
                {
                        if (fPerformanceTimeEvent == NULL) {
                                // Stale event message. There is a natural race
                                // condition when removing the event from the queue,
                                // it may have already fired, but we have not processed
                                // the message yet. Simply ignore the event.
                                break;
                        }

                        bigtime_t eventTime;
                        message->FindInt64("time", &eventTime);
//                      bigtime_t now = system_time();
                        fPerformanceTimeEvent = NULL;

//                      SetPerformanceTime(TimeForRealTime(now));
                        SetPerformanceTime(TimeForRealTime(eventTime));
//TRACE("MSG_EVENT: rt: %lld, pt: %lld\n", now, fPerformanceTime);
//printf("MSG_EVENT: et: %lld, rt: %lld, pt: %lld\n", eventTime, now, fPerformanceTime);
                        break;
                }

                case MSG_PLAYBACK_FORCE_UPDATE:
                {
                        int64 frame;
                        if (message->FindInt64("frame", &frame) < B_OK)
                                frame = CurrentFrame();
                        SetCurrentFrame(frame);
                        break;
                }

                case MSG_PLAYBACK_SET_RANGE:
                {
                        int64 startFrame = _LastState()->start_frame;
                        int64 endFrame = _LastState()->end_frame;
                        message->FindInt64("start frame", &startFrame);
                        message->FindInt64("end frame", &endFrame);
                        if (startFrame != _LastState()->start_frame
                                || endFrame != _LastState()->end_frame) {
                                PlayingState* state = new PlayingState(*_LastState());
                                state->start_frame = startFrame;
                                state->end_frame = endFrame;
                                _PushState(state, true);
                        }
                        break;
                }

                case MSG_PLAYBACK_SET_VISIBLE:
                {
                        int64 startFrame = _LastState()->first_visible_frame;
                        int64 endFrame = _LastState()->last_visible_frame;
                        message->FindInt64("start frame", &startFrame);
                        message->FindInt64("end frame", &endFrame);
                        if (startFrame != _LastState()->first_visible_frame
                                || endFrame != _LastState()->last_visible_frame) {
                                PlayingState* state = new PlayingState(*_LastState());
                                state->first_visible_frame = startFrame;
                                state->last_visible_frame = endFrame;
                                _PushState(state, true);
                        }
                        break;
                }

                case MSG_PLAYBACK_SET_LOOP_MODE:
                {
                        int32 loopMode = _LastState()->loop_mode;
                        message->FindInt32("mode", &loopMode);
                        if (loopMode != _LastState()->loop_mode) {
                                PlayingState* state = new PlayingState(*_LastState());
                                state->loop_mode = loopMode;
                                _PushState(state, true);
                        }
                        break;
                }

                // other messages
                default:
                        BLooper::MessageReceived(message);
                        break;
        }
}

// #pragma mark - playback control


void
PlaybackManager::StartPlaying(bool atBeginning)
{
        TRACE("PlaybackManager::StartPlaying()\n");
        int32 playMode = PlayMode();
        if (playMode == MODE_PLAYING_PAUSED_FORWARD)
                SetPlayMode(MODE_PLAYING_FORWARD, !atBeginning);
        if (playMode == MODE_PLAYING_PAUSED_BACKWARD)
                SetPlayMode(MODE_PLAYING_BACKWARD, !atBeginning);
}


void
PlaybackManager::StopPlaying()
{
        TRACE("PlaybackManager::StopPlaying()\n");
        int32 playMode = PlayMode();
        if (playMode == MODE_PLAYING_FORWARD)
                SetPlayMode(MODE_PLAYING_PAUSED_FORWARD, true);
        if (playMode == MODE_PLAYING_BACKWARD)
                SetPlayMode(MODE_PLAYING_PAUSED_BACKWARD, true);
}


void
PlaybackManager::TogglePlaying(bool atBeginning)
{
        // playmodes (paused <-> playing) are the negative of each other
        SetPlayMode(-PlayMode(), !atBeginning);
}


void
PlaybackManager::PausePlaying()
{
        if (PlayMode() > 0)
                TogglePlaying();
}


bool
PlaybackManager::IsPlaying() const
{
        return (PlayMode() > 0);
}


int32
PlaybackManager::PlayMode() const
{
        if (!_LastState())
                return MODE_PLAYING_PAUSED_FORWARD;
        return _LastState()->play_mode;
}


int32
PlaybackManager::LoopMode() const
{
        if (!_LastState())
                return LOOPING_ALL;
        return _LastState()->loop_mode;
}


bool
PlaybackManager::IsLoopingEnabled() const
{
        if (!_LastState())
                return true;
        return _LastState()->looping_enabled;
}


int64
PlaybackManager::CurrentFrame() const
{
        return PlaylistFrameAtFrame(FrameForTime(fPerformanceTime));
}


float
PlaybackManager::Speed() const
{
#if SUPPORT_SPEED_CHANGES
        if (!_LastState())
                return 1.0;
        return _LastSpeedInfo()->set_speed;
#else
        return 1.0;
#endif
}


void
PlaybackManager::SetFramesPerSecond(float framesPerSecond)
{
        if (framesPerSecond != fFrameRate) {
                fFrameRate = framesPerSecond;
                NotifyFPSChanged(fFrameRate);
        }
}


float
PlaybackManager::FramesPerSecond() const
{
        return fFrameRate;
}


void
PlaybackManager::FrameDropped() const
{
        NotifyFrameDropped();
}


void
PlaybackManager::DurationChanged()
{
        if (!_LastState())
                return;
        int32 frameCount = Duration();
        if (frameCount != _LastState()->frame_count) {
                PlayingState* state = new PlayingState(*_LastState());
                state->frame_count = frameCount;
                state->max_frame_count = frameCount;

                _PushState(state, true);
        }
}

/*! Creates a new playing state that equals the previous one aside from
        its current frame which is set to /frame/.
        The new state will be activated as soon as possible. */
void
PlaybackManager::SetCurrentFrame(int64 frame)
{
        if (CurrentFrame() == frame) {
                NotifySeekHandled(frame);
                return;
        }
        PlayingState* newState = new PlayingState(*_LastState());
        newState->current_frame = frame;
        newState->is_seek_request = true;
        _PushState(newState, false);
}


/*!     Creates a new playing state that equals the previous one aside from
        its playing mode which is set to /mode/.
        The new state will be activated as soon as possible.
        If /continuePlaying/ is true and the new state is a `not stopped'
        state, playing continues at the frame the last state reaches when the
        new state becomes active (or the next frame in the playing range).
        If /continuePlaying/ is false, playing starts at the beginning of the
        playing range (taking the playing direction into consideration). */
void
PlaybackManager::SetPlayMode(int32 mode, bool continuePlaying)
{
//printf("PlaybackManager::SetPlayMode(%ld, %d)\n", mode, continuePlaying);
        PlayingState* newState = new PlayingState(*_LastState());
        newState->play_mode = mode;
        // Jump to the playing start frame if we should not continue, where we
        // stop.
        if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0))
                newState->current_frame = _PlayingStartFrameFor(newState);
        _PushState(newState, continuePlaying);
        NotifyPlayModeChanged(mode);
}


/*!     Creates a new playing state that equals the previous one aside from
        its loop mode which is set to /mode/.
        The new state will be activated as soon as possible.
        If /continuePlaying/ is true and the new state is a `not stopped'
        state, playing continues at the frame the last state reaches when the
        new state becomes active (or the next frame in the playing range).
        If /continuePlaying/ is false, playing starts at the beginning of the
        playing range (taking the playing direction into consideration). */
void
PlaybackManager::SetLoopMode(int32 mode, bool continuePlaying)
{
        PlayingState* newState = new PlayingState(*_LastState());
        newState->loop_mode = mode;
        // Jump to the playing start frame if we should not continue, where we
        // stop.
        if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0))
                newState->current_frame = _PlayingStartFrameFor(newState);
        _PushState(newState, continuePlaying);
        NotifyLoopModeChanged(mode);
}


/*      Creates a new playing state that equals the previous one aside from
        its looping enabled flag which is set to /enabled/.
        The new state will be activated as soon as possible.
        If /continuePlaying/ is true and the new state is a `not stopped'
        state, playing continues at the frame the last state reaches when the
        new state becomes active (or the next frame in the playing range).
        If /continuePlaying/ is false, playing starts at the beginning of the
        playing range (taking the playing direction into consideration). */
void
PlaybackManager::SetLoopingEnabled(bool enabled, bool continuePlaying)
{
        PlayingState* newState = new PlayingState(*_LastState());
        newState->looping_enabled = enabled;
        // Jump to the playing start frame if we should not continue, where we
        // stop.
        if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0))
                newState->current_frame = _PlayingStartFrameFor(newState);
        _PushState(newState, continuePlaying);
        NotifyLoopingEnabledChanged(enabled);
}


void
PlaybackManager::SetSpeed(float speed)
{
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* lastSpeed = _LastSpeedInfo();
        if (speed != lastSpeed->set_speed) {
                SpeedInfo* info = new SpeedInfo(*lastSpeed);
                info->activation_frame = NextFrame();
                info->set_speed = speed;
                if (_PlayingDirectionFor(_StateAtFrame(info->activation_frame)) != 0)
                        info->speed = info->set_speed;
                else
                        info->speed = 1.0;
                _PushSpeedInfo(info);
        }
#endif
}


// #pragma mark -


/*!     Returns the first frame at which a new playing state could become active,
        that is the first frame for that neither the audio nor the video producer
        have fetched data.*/
int64
PlaybackManager::NextFrame() const
{
        if (fNoAudio)
                return FrameForTime(fCurrentVideoTime - 1) + 1;

        return FrameForTime(max((bigtime_t)fCurrentAudioTime,
                (bigtime_t)fCurrentVideoTime) - 1) + 1;
}


//! Returns the Playlist frame for NextFrame().
int64
PlaybackManager::NextPlaylistFrame() const
{
        return PlaylistFrameAtFrame(NextFrame());
}


int64
PlaybackManager::FirstPlaybackRangeFrame()
{
        PlayingState* state = _StateAtFrame(CurrentFrame());
        switch (state->loop_mode) {
                case LOOPING_RANGE:
                        return state->start_frame;
                case LOOPING_SELECTION:
                        // TODO: ...
                        return 0;
                case LOOPING_VISIBLE:
                        return state->first_visible_frame;

                case LOOPING_ALL:
                default:
                        return 0;
        }
}


int64
PlaybackManager::LastPlaybackRangeFrame()
{
        PlayingState* state = _StateAtFrame(CurrentFrame());
        switch (state->loop_mode) {
                case LOOPING_RANGE:
                        return state->end_frame;
                case LOOPING_SELECTION:
                        // TODO: ...
                        return state->frame_count - 1;
                case LOOPING_VISIBLE:
                        return state->last_visible_frame;

                case LOOPING_ALL:
                default:
                        return state->frame_count - 1;
        }
}


// #pragma mark -


int64
PlaybackManager::StartFrameAtFrame(int64 frame)
{
        return _StateAtFrame(frame)->start_frame;
}


int64
PlaybackManager::StartFrameAtTime(bigtime_t time)
{
        return _StateAtTime(time)->start_frame;
}


int64
PlaybackManager::EndFrameAtFrame(int64 frame)
{
        return _StateAtTime(frame)->end_frame;
}


int64
PlaybackManager::EndFrameAtTime(bigtime_t time)
{
        return _StateAtTime(time)->end_frame;
}


int64
PlaybackManager::FrameCountAtFrame(int64 frame)
{
        return _StateAtTime(frame)->frame_count;
}


int64
PlaybackManager::FrameCountAtTime(bigtime_t time)
{
        return _StateAtTime(time)->frame_count;
}


int32
PlaybackManager::PlayModeAtFrame(int64 frame)
{
        return _StateAtTime(frame)->play_mode;
}


int32
PlaybackManager::PlayModeAtTime(bigtime_t time)
{
        return _StateAtTime(time)->play_mode;
}


int32
PlaybackManager::LoopModeAtFrame(int64 frame)
{
        return _StateAtTime(frame)->loop_mode;
}


int32
PlaybackManager::LoopModeAtTime(bigtime_t time)
{
        return _StateAtTime(time)->loop_mode;
}


/*!     Returns which Playlist frame should be displayed at (performance) video
        frame /frame/. Additionally the playing direction (0, 1, -1) is returned,
        as well as if a new playing state becomes active with this frame.
        A new playing state is installed, if either some data directly concerning
        the playing (play mode, loop mode, playing ranges, selection...) or the
        Playlist has changed. */
int64
PlaybackManager::PlaylistFrameAtFrame(int64 frame, int32& playingDirection,
        bool& newState) const
{
//TRACE("PlaybackManager::PlaylistFrameAtFrame(%lld)\n", frame);
        PlayingState* state = _StateAtFrame(frame);
        newState = (state->activation_frame == frame);
        playingDirection = _PlayingDirectionFor(state);
//TRACE("playing state: activation frame: %lld, current frame: %lld, dir: %ld\n",
//state->activation_frame, state->current_frame, state->play_mode);
        // The first part of the index calculation is invariable for a state. We
        // could add it to the state data.
        int64 result = state->current_frame;
        if (playingDirection != 0) {
//              int64 index = _RangeFrameForFrame(state, state->current_frame)
                int64 index = state->range_index
                        + (frame - state->activation_frame) * playingDirection;
                result = _FrameForRangeFrame(state, index);
        }
//TRACE("PlaybackManager::PlaylistFrameAtFrame() done: %lld\n", result);
//printf("PlaybackManager::PlaylistFrameAtFrame(%lld): %lld, direction: %ld\n",
//      frame, result, playingDirection);
        return result;
}


int64
PlaybackManager::PlaylistFrameAtFrame(int64 frame, int32& playingDirection) const
{
        bool newState;
        return PlaylistFrameAtFrame(frame, playingDirection, newState);
}


int64
PlaybackManager::PlaylistFrameAtFrame(int64 frame) const
{
        bool newState;
        int32 playingDirection;
        return PlaylistFrameAtFrame(frame, playingDirection, newState);
}


/*!     Returns the index of the next frame after /startFrame/ at which a
        playing state or speed change occurs or /endFrame/, if this happens to be
        earlier. */
int64
PlaybackManager::NextChangeFrame(int64 startFrame, int64 endFrame) const
{
        int32 startIndex = _IndexForFrame(startFrame);
        int32 endIndex = _IndexForFrame(endFrame);
        if (startIndex < endIndex)
                endFrame = _StateAt(startIndex + 1)->activation_frame;
#if SUPPORT_SPEED_CHANGES
        startIndex = _SpeedInfoIndexForFrame(startFrame);
        endIndex = _SpeedInfoIndexForFrame(endFrame);
        if (startIndex < endIndex)
                endFrame = _SpeedInfoAt(startIndex + 1)->activation_frame;
#endif
        return endFrame;
}


/*!     Returns the next time after /startTime/ at which a playing state or
        speed change occurs or /endTime/, if this happens to be earlier. */
bigtime_t
PlaybackManager::NextChangeTime(bigtime_t startTime, bigtime_t endTime) const
{
        int32 startIndex = _IndexForTime(startTime);
        int32 endIndex = _IndexForTime(endTime);
        if (startIndex < endIndex)
                endTime = TimeForFrame(_StateAt(startIndex + 1)->activation_frame);
#if SUPPORT_SPEED_CHANGES
        startIndex = _SpeedInfoIndexForTime(startTime);
        endIndex = _SpeedInfoIndexForTime(endTime);
        if (startIndex < endIndex)
                endTime = TimeForFrame(_SpeedInfoAt(startIndex + 1)->activation_frame);
#endif
        return endTime;
}


/*!     Returns a contiguous Playlist frame interval for a given frame interval.
        The returned interval may be smaller than the supplied one. Therefore
        the supplied /endFrame/ is adjusted.
        The value written to /xEndFrame/ is the first frame that doesn't belong
        to the interval. /playingDirection/ may either be -1 for backward,
        1 for forward or 0 for not playing. */
void
PlaybackManager::GetPlaylistFrameInterval(int64 startFrame, int64& endFrame,
        int64& xStartFrame, int64& xEndFrame, int32& playingDirection) const
{
        // limit the interval to one state and speed info
        endFrame = NextChangeFrame(startFrame, endFrame);
        // init return values
        xStartFrame = PlaylistFrameAtFrame(startFrame);
        xEndFrame = xStartFrame;
        playingDirection = _PlayingDirectionFor(_StateAtFrame(startFrame));
        // Step through the interval and check whether the respective Playlist
        // frames belong to the Playlist interval.
        bool endOfInterval = false;
        int64 intervalLength = 0;
        while (startFrame < endFrame && !endOfInterval) {
                intervalLength++;
                startFrame++;
                xEndFrame += playingDirection;
                endOfInterval = (PlaylistFrameAtFrame(startFrame) != xEndFrame);
        }
        // order the interval bounds, if playing backwards
        if (xStartFrame > xEndFrame) {
                swap(xStartFrame, xEndFrame);
                xStartFrame++;
                xEndFrame++;
        }
}


/*!     The same as GetPlaylistFrameInterval() just for time instead of frame
        intervals. Note, that /startTime/ and /endTime/ measure
        performance times whereas /xStartTime/ and /xEndTime/ specifies an
        Playlist time interval. In general it does not hold
        xEndTime - xStartTime == endTime - startTime, even if playing (think
        of a playing speed != 1.0). */
void
PlaybackManager::GetPlaylistTimeInterval(bigtime_t startTime,
        bigtime_t& endTime, bigtime_t& xStartTime, bigtime_t& xEndTime,
        float& playingSpeed) const
{
        // Get the frames that bound the given time interval. The end frame might
        // be greater than necessary, but that doesn't harm.
        int64 startFrame = FrameForTime(startTime);
        int64 endFrame = FrameForTime(endTime) + 1;
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* info = _SpeedInfoForFrame(startFrame)->speed;
#endif
        // Get the Playlist frame interval that belongs to the frame interval.
        int64 xStartFrame;
        int64 xEndFrame;
        int32 playingDirection;
        GetPlaylistFrameInterval(startFrame, endFrame, xStartFrame, xEndFrame,
                playingDirection);
        // Calculate the performance time interval end/length.
        bigtime_t endTimeForFrame = TimeForFrame(endFrame);
        endTime = min(endTime, endTimeForFrame);
        bigtime_t intervalLength = endTime - startTime;

        // Finally determine the time bounds for the Playlist interval (depending
        // on the playing direction).
        switch (playingDirection) {
                // forward
                case 1:
                {
#if SUPPORT_SPEED_CHANGES
// TODO: The current method does not handle the times the same way.
//       It may happen, that for the same performance time different
//       Playlist times (within a frame) are returned when passing it
//       one time as a start time and another time as an end time.
                        xStartTime = PlaylistTimeForFrame(xStartFrame)
                                + bigtime_t(double(startTime - TimeForFrame(startFrame))
                                        * info->speed);
                        xEndTime = xStartTime
                                + bigtime_t((double)intervalLength * info->speed);
#else
                        xStartTime = PlaylistTimeForFrame(xStartFrame)
                                + startTime - TimeForFrame(startFrame);
                        xEndTime = xStartTime + intervalLength;
#endif
                        break;
                }
                // backward
                case -1:
                {
#if SUPPORT_SPEED_CHANGES
                        xEndTime = PlaylistTimeForFrame(xEndFrame)
                                - bigtime_t(double(startTime - TimeForFrame(startFrame))
                                        * info->speed);
                        xStartTime = xEndTime
                                - bigtime_t((double)intervalLength * info->speed);
#else
                        xEndTime = PlaylistTimeForFrame(xEndFrame)
                                - startTime + TimeForFrame(startFrame);
                        xStartTime = xEndTime - intervalLength;
#endif
                        break;
                }
                // not playing
                case 0:
                default:
                        xStartTime = PlaylistTimeForFrame(xStartFrame);
                        xEndTime = xStartTime;
                        break;
        }
#if SUPPORT_SPEED_CHANGES
        playingSpeed = (float)playingDirection * info->speed;
#else
        playingSpeed = (float)playingDirection;
#endif
}


/*!     Returns the frame that is being performed at the supplied time.
        It holds TimeForFrame(frame) <= time < TimeForFrame(frame + 1). */
int64
PlaybackManager::FrameForTime(bigtime_t time) const
{
//TRACE("PlaybackManager::FrameForTime(%lld)\n", time);
        // In order to avoid problems caused by rounding errors, we check
        // if for the resulting frame holds
        // TimeForFrame(frame) <= time < TimeForFrame(frame + 1).
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* info = _SpeedInfoForTime(time);
if (!info) {
        fprintf(stderr, "PlaybackManager::FrameForTime() - no SpeedInfo!\n");
        return 0;
}
        int64 frame = (int64)(((double)time - info->activation_time)
                * (double)fFrameRate * info->speed / 1000000.0)
                + info->activation_frame;

#else
        int64 frame = (int64)((double)time * (double)fFrameRate / 1000000.0);
#endif
        if (TimeForFrame(frame) > time)
                frame--;
        else if (TimeForFrame(frame + 1) <= time)
                frame++;
//TRACE("PlaybackManager::FrameForTime() done: %lld\n", frame);
//printf("PlaybackManager::FrameForTime(%lld): %lld\n", time, frame);
        return frame;
}


/*!     Returns the time at which the supplied frame should be performed (started
        to be performed). */
bigtime_t
PlaybackManager::TimeForFrame(int64 frame) const
{
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* info = _SpeedInfoForFrame(frame);
if (!info) {
        fprintf(stderr, "PlaybackManager::TimeForFrame() - no SpeedInfo!\n");
        return 0;
}
        return (bigtime_t)((double)(frame - info->activation_frame) * 1000000.0
                                           / ((double)fFrameRate * info->speed))
                   + info->activation_time;
#else
        return (bigtime_t)((double)frame * 1000000.0 / (double)fFrameRate);
#endif
}


/*!     Returns the Playlist frame for an Playlist time.
        It holds PlaylistTimeForFrame(frame) <= time <
        PlaylistTimeForFrame(frame + 1). */
int64
PlaybackManager::PlaylistFrameForTime(bigtime_t time) const
{
        // In order to avoid problems caused by rounding errors, we check
        // if for the resulting frame holds
        // PlaylistTimeForFrame(frame) <= time < PlaylistTimeForFrame(frame + 1).
        int64 frame = (int64)((double)time * (double)fFrameRate / 1000000.0);
        if (TimeForFrame(frame) > time)
                frame--;
        else if (TimeForFrame(frame + 1) <= time)
                frame++;
        return frame;
}


//! Returns the Playlist start time for an Playlist frame.
bigtime_t
PlaybackManager::PlaylistTimeForFrame(int64 frame) const
{
        return (bigtime_t)((double)frame * 1000000.0 / (double)fFrameRate);
}


//! To be called when done with all activities concerning audio before /time/.
void
PlaybackManager::SetCurrentAudioTime(bigtime_t time)
{
        TRACE("PlaybackManager::SetCurrentAudioTime(%lld)\n", time);
        bigtime_t lastFrameTime = _TimeForLastFrame();
        fCurrentAudioTime = time;
        bigtime_t newLastFrameTime = _TimeForLastFrame();
        if (lastFrameTime != newLastFrameTime) {
                if (fPerformanceTimeEvent == NULL) {
                        bigtime_t eventTime = RealTimeForTime(newLastFrameTime);
                        fPerformanceTimeEvent = new MessageEvent(eventTime, this);
                        EventQueue::Default().AddEvent(fPerformanceTimeEvent);
                }
                _CheckStopPlaying();
        }
}


//! To be called when done with all activities concerning video before /frame/.
void
PlaybackManager::SetCurrentVideoFrame(int64 frame)
{
        SetCurrentVideoTime(TimeForFrame(frame));
}


//! To be called when done with all activities concerning video before /time/.
void
PlaybackManager::SetCurrentVideoTime(bigtime_t time)
{
        TRACE("PlaybackManager::SetCurrentVideoTime(%lld)\n", time);
        bigtime_t lastFrameTime = _TimeForLastFrame();
        fCurrentVideoTime = time;
        bigtime_t newLastFrameTime = _TimeForLastFrame();
        if (lastFrameTime != newLastFrameTime) {
                if (fPerformanceTimeEvent == NULL) {
                        bigtime_t eventTime = RealTimeForTime(newLastFrameTime);
                        fPerformanceTimeEvent = new MessageEvent(eventTime, this);
                        EventQueue::Default().AddEvent(fPerformanceTimeEvent);
                }
                _CheckStopPlaying();
        }
}


//! To be called as soon as video frame /frame/ is being performed.
void
PlaybackManager::SetPerformanceFrame(int64 frame)
{
        SetPerformanceTime(TimeForFrame(frame));
}


/*!     Similar to SetPerformanceFrame() just with a time instead of a frame
        argument. */
void
PlaybackManager::SetPerformanceTime(bigtime_t time)
{
        int64 oldCurrentFrame = CurrentFrame();
        fPerformanceTime = time;
        _UpdateStates();
        _UpdateSpeedInfos();
        int64 currentFrame = CurrentFrame();

//printf("PlaybackManager::SetPerformanceTime(%lld): %ld -> %ld\n",
//      time, oldCurrentFrame, currentFrame);

        if (currentFrame != oldCurrentFrame)
                NotifyCurrentFrameChanged(currentFrame);
}


// #pragma mark - Listeners


void
PlaybackManager::AddListener(PlaybackListener* listener)
{
        if (!listener)
                return;

        if (!Lock())
                return;

        if (!fListeners.HasItem(listener) && fListeners.AddItem(listener)) {
                // bring listener up2date, if we have been initialized
                if (_LastState()) {
                        listener->PlayModeChanged(PlayMode());
                        listener->LoopModeChanged(LoopMode());
                        listener->LoopingEnabledChanged(IsLoopingEnabled());
                        listener->VideoBoundsChanged(VideoBounds());
                        listener->FramesPerSecondChanged(FramesPerSecond());
                        listener->SpeedChanged(Speed());
                        listener->CurrentFrameChanged(CurrentFrame());
                }
        }

        Unlock();
}


void
PlaybackManager::RemoveListener(PlaybackListener* listener)
{
        if (listener && Lock()) {
                fListeners.RemoveItem(listener);
                Unlock();
        }
}


void
PlaybackManager::NotifyPlayModeChanged(int32 mode) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->PlayModeChanged(mode);
        }
}


void
PlaybackManager::NotifyLoopModeChanged(int32 mode) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->LoopModeChanged(mode);
        }
}


void
PlaybackManager::NotifyLoopingEnabledChanged(bool enabled) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->LoopingEnabledChanged(enabled);
        }
}


void
PlaybackManager::NotifyVideoBoundsChanged(BRect bounds) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->VideoBoundsChanged(bounds);
        }
}


void
PlaybackManager::NotifyFPSChanged(float fps) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->FramesPerSecondChanged(fps);
        }
}


void
PlaybackManager::NotifyCurrentFrameChanged(int64 frame) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->CurrentFrameChanged(frame);
        }
}


void
PlaybackManager::NotifySpeedChanged(float speed) const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->SpeedChanged(speed);
        }
}


void
PlaybackManager::NotifyFrameDropped() const
{
        for (int32 i = 0;
                 PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i);
                 i++) {
                listener->FrameDropped();
        }
}


void
PlaybackManager::NotifyStopFrameReached() const
{
        // not currently implemented in PlaybackListener interface
}


void
PlaybackManager::NotifySeekHandled(int64 frame) const
{
        // not currently implemented in PlaybackListener interface
}


void
PlaybackManager::PrintState(PlayingState* state)
{
        TRACE("state: activation frame: %lld, current frame: %lld, "
                   "start frame: %lld, end frame: %lld, frame count: %lld, "
                   "first visible: %lld, last visible: %lld, selection (...), "
                   "play mode: %ld, loop mode: %ld\n",
                        state->activation_frame,
                        state->current_frame,
                        state->start_frame,
                        state->end_frame,
                        state->frame_count,
                        state->first_visible_frame,
                        state->last_visible_frame,
//                      state->selection,
                        state->play_mode,
                        state->loop_mode);
}


void
PlaybackManager::PrintStateAtFrame(int64 frame)
{
        TRACE("frame %lld: ", frame);
        PrintState(_StateAtFrame(frame));
}


// #pragma mark -


/*!     Appends the supplied state to the list of states. If the state would
        become active at the same time as _LastState() the latter is removed
        and deleted. However, the activation time for the new state is adjusted,
        so that it is >= that of the last state and >= the current audio and
        video time. If the additional parameter /adjustCurrentFrame/ is true,
        the new state's current frame is tried to be set to the frame that is
        reached at the time the state will become active. In every case
        it is ensured that the current frame lies within the playing range
        (if playing). */
void
PlaybackManager::_PushState(PlayingState* state, bool adjustCurrentFrame)
{
//      if (!_LastState())
//              debugger("PlaybackManager::_PushState() used before Init()\n");

TRACE("PlaybackManager::_PushState()\n");
        if (state == NULL)
                return;

        // unset fStopPlayingFrame
        int64 oldStopPlayingFrame = fStopPlayingFrame;
        fStopPlayingFrame = -1;
        // get last state
        PlayingState* lastState = _LastState();
        int64 activationFrame = max(max(state->activation_frame,
                                                                        lastState->activation_frame),
                                                                NextFrame());
        int64 currentFrame = 0;
        // remember the current frame, if necessary
        if (adjustCurrentFrame) {
                currentFrame = PlaylistFrameAtFrame(activationFrame);
                if (currentFrame == CurrentFrame()) {
                        // Seems to be paused, force using the next frame
                        currentFrame++;
                }
        }
        // Check whether the last state has already become active
        // (NOTE: We may want to keep the last state, if it is not active,
        //  but then the new state should become active after the last one.
        //  Thus we had to replace `NextFrame()' with `activationFrame'.)
TRACE("  state activation frame: %lld, last state activation frame: %lld, "
"NextFrame(): %lld, currentFrame: %lld, next currentFrame: %lld\n",
state->activation_frame, lastState->activation_frame, NextFrame(),
CurrentFrame(), currentFrame);
        if (lastState->activation_frame >= NextFrame()) {
                // it isn't -- remove it
                fStates.RemoveItem(fStates.CountItems() - 1);
TRACE("deleting last \n");
PrintState(lastState);
                _NotifySeekHandledIfNecessary(lastState);
                delete lastState;
        } else {
                // it is -- keep it
        }
        // adjust the new state's current frame and activation frame
        if (adjustCurrentFrame)
                state->current_frame = currentFrame;
        int32 playingDirection = _PlayingDirectionFor(state);
        if (playingDirection != 0) {
                state->current_frame
                        = _NextFrameInRange(state, state->current_frame);
        } else {
                // If not playing, we check at least, if the current frame lies
                // within the interval [0, max_frame_count).
                if (state->current_frame >= state->max_frame_count)
                        state->current_frame = state->max_frame_count - 1;
                if (state->current_frame < 0)
                        state->current_frame = 0;
        }
        state->range_index = _RangeFrameForFrame(state, state->current_frame);
        state->activation_frame = activationFrame;
        fStates.AddItem(state);
PrintState(state);
TRACE("_PushState: state count: %ld\n", fStates.CountItems());
#if SUPPORT_SPEED_CHANGES
        // push a new speed info
        SpeedInfo* speedInfo = new SpeedInfo(*_LastSpeedInfo());
        if (playingDirection == 0)
                speedInfo->speed = 1.0;
        else
                speedInfo->speed = speedInfo->set_speed;
        speedInfo->activation_frame = state->activation_frame;
        _PushSpeedInfo(speedInfo);
#endif
        // If the new state is a playing state and looping is turned off,
        // determine when playing shall stop.
        if (playingDirection != 0 && !state->looping_enabled) {
                int64 startFrame, endFrame, frameCount;
                _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
                if (playingDirection == -1)
                        swap(startFrame, endFrame);
                // If we shall stop at the frame we start, set the current frame
                // to the beginning of the range.
                // We have to take care, since this state may equal the one
                // before (or probably differs in just one (unimportant)
                // parameter). This happens for instance, if the user changes the
                // data or start/end frame... while playing. In this case setting
                // the current frame to the start frame is unwanted. Therefore
                // we check whether the previous state was intended to stop
                // at the activation frame of this state.
                if (oldStopPlayingFrame != state->activation_frame
                        && state->current_frame == endFrame && frameCount > 1) {
                        state->current_frame = startFrame;
                        state->range_index
                                = _RangeFrameForFrame(state, state->current_frame);
                }
                if (playingDirection == 1) {    // forward
                        fStopPlayingFrame = state->activation_frame
                                                                + frameCount - state->range_index - 1;
                } else {                                                // backwards
                        fStopPlayingFrame = state->activation_frame
                                                                + state->range_index;
                }
                _CheckStopPlaying();
        }
TRACE("PlaybackManager::_PushState() done\n");
}


/*!     Removes and deletes all states that are obsolete, that is which stopped
        being active at or before the current audio and video time. */
void
PlaybackManager::_UpdateStates()
{
//      int32 firstActive = min(_IndexForTime(fCurrentAudioTime),
//                                                      _IndexForTime(fCurrentVideoTime));
        // Performance time should always be the least one.
        int32 firstActive = _IndexForTime(fPerformanceTime);
//TRACE("firstActive: %ld, numStates: %ld\n", firstActive, fStates.CountItems());
        for (int32 i = 0; i < firstActive; i++) {
                PlayingState* state = _StateAt(i);
                _NotifySeekHandledIfNecessary(state);
                delete state;
        }
        if (firstActive > 0)
{
                fStates.RemoveItems(0, firstActive);
TRACE("_UpdateStates: states removed: %ld, state count: %ld\n",
firstActive, fStates.CountItems());
}
        _NotifySeekHandledIfNecessary(_StateAt(0));
}


/*!     Returns the index of the state, that is active at frame /frame/.
        The index of the first frame (0) is returned, if /frame/ is even less
        than the frame at which this state becomes active. */
int32
PlaybackManager::_IndexForFrame(int64 frame) const
{
        int32 index = 0;
        PlayingState* state;
        while (((state = _StateAt(index + 1))) && state->activation_frame <= frame)
                index++;
        return index;
}

/*!     Returns the index of the state, that is active at time /time/.
        The index of the first frame (0) is returned, if /time/ is even less
        than the time at which this state becomes active. */
int32
PlaybackManager::_IndexForTime(bigtime_t time) const
{
        return _IndexForFrame(FrameForTime(time));
}


//! Returns the state that reflects the most recent changes.
PlaybackManager::PlayingState*
PlaybackManager::_LastState() const
{
        return _StateAt(fStates.CountItems() - 1);
}


PlaybackManager::PlayingState*
PlaybackManager::_StateAt(int32 index) const
{
        return (PlayingState*)fStates.ItemAt(index);
}


PlaybackManager::PlayingState*
PlaybackManager::_StateAtFrame(int64 frame) const
{
        return _StateAt(_IndexForFrame(frame));
}


PlaybackManager::PlayingState*
PlaybackManager::_StateAtTime(bigtime_t time) const
{
        return _StateAt(_IndexForTime(time));
}


int32
PlaybackManager::_PlayingDirectionFor(int32 playingMode)
{
        int32 direction = 0;
        switch (playingMode) {
                case MODE_PLAYING_FORWARD:
                        direction = 1;
                        break;
                case MODE_PLAYING_BACKWARD:
                        direction = -1;
                        break;
                case MODE_PLAYING_PAUSED_FORWARD:
                case MODE_PLAYING_PAUSED_BACKWARD:
                        break;
        }
        return direction;
}


int32
PlaybackManager::_PlayingDirectionFor(PlayingState* state)
{
        return _PlayingDirectionFor(state->play_mode);
}


/*!     Returns the Playlist frame range that bounds the playing range of a given
        state.
        \a startFrame is the lower and \a endFrame the upper bound of the range,
        and \a frameCount the number of frames in the range; it is guaranteed to
        be >= 1, even for an empty selection. */
void
PlaybackManager::_GetPlayingBoundsFor(PlayingState* state, int64& startFrame,
        int64& endFrame, int64& frameCount)
{
        switch (state->loop_mode) {
                case LOOPING_ALL:
                        startFrame = 0;
                        endFrame = max(startFrame, state->frame_count - 1);
                        frameCount = endFrame - startFrame + 1;
                        break;
                case LOOPING_RANGE:
                        startFrame = state->start_frame;
                        endFrame = state->end_frame;
                        frameCount = endFrame - startFrame + 1;
                        break;
//              case LOOPING_SELECTION:
//                      if (!state->selection.IsEmpty()) {
//                              startFrame = state->selection.FirstIndex();
//                              endFrame = state->selection.LastIndex();
//                              frameCount = state->selection.CountIndices();
//TRACE("  LOOPING_SELECTION: %lld - %lld (%lld)\n", startFrame, endFrame,
//frameCount);
//                      } else {
//                              startFrame = state->current_frame;
//                              endFrame = state->current_frame;
//                              frameCount = 1;
//                      }
//                      break;
                case LOOPING_VISIBLE:
                        startFrame = state->first_visible_frame;
                        endFrame = state->last_visible_frame;
                        frameCount = endFrame - startFrame + 1;
                        break;
        }
}


/*!     Returns the frame at which playing would start for a given playing
        state. If the playing mode for the supplied state specifies a stopped
        or undefined mode, the result is the state's current frame. */
int64
PlaybackManager::_PlayingStartFrameFor(PlayingState* state)
{
        int64 startFrame, endFrame, frameCount, frame = 0;
        _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
        switch (_PlayingDirectionFor(state)) {
                case -1:
                        frame = endFrame;
                        break;
                case 1:
                        frame = startFrame;
                        break;
                default:
                        frame = state->current_frame;
                        break;
        }
        return frame;
}


/*!     Returns the frame at which playing would end for a given playing
        state. If the playing mode for the supplied state specifies a stopped
        or undefined mode, the result is the state's current frame. */
int64
PlaybackManager::_PlayingEndFrameFor(PlayingState* state)
{
        int64 startFrame, endFrame, frameCount, frame = 0;
        _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
        switch (_PlayingDirectionFor(state)) {
                case -1:
                        frame = startFrame;
                        break;
                case 1:
                        frame = endFrame;
                        break;
                default:
                        frame = state->current_frame;
                        break;
        }
        return frame;
}


/*!     Returns the index that the supplied frame has within a playing range.
        If the state specifies a not-playing mode, 0 is returned. The supplied
        frame has to lie within the bounds of the playing range, but it doesn't
        need to be contained, e.g. if the range is not contiguous. In this case
        the index of the next frame within the range (in playing direction) is
        returned. */
int64
PlaybackManager::_RangeFrameForFrame(PlayingState* state, int64 frame)
{
TRACE("PlaybackManager::_RangeFrameForFrame(%lld)\n", frame);
        int64 startFrame, endFrame, frameCount;
        int64 index = 0;
        _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
TRACE("  start frame: %lld, end frame: %lld, frame count: %lld\n",
startFrame, endFrame, frameCount);
        switch (state->loop_mode) {
                case LOOPING_ALL:
                case LOOPING_RANGE:
                case LOOPING_VISIBLE:
                        index = frame - startFrame;
                        break;
        }
TRACE("PlaybackManager::_RangeFrameForFrame() done: %lld\n", index);
        return index;
}


/*!     Returns the Playlist frame for a playing range index. /index/ doesn't need
        to be in the playing range -- it is mapped into. */
int64
PlaybackManager::_FrameForRangeFrame(PlayingState* state, int64 index)
{
TRACE("PlaybackManager::_FrameForRangeFrame(%lld)\n", index);
        int64 startFrame, endFrame, frameCount;
        _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
TRACE("  frame range: %lld - %lld, count: %lld\n", startFrame, endFrame,
frameCount);
        // map the index into the index interval of the playing range
        if (frameCount > 1)
                index = (index % frameCount + frameCount) % frameCount;

        // get the frame for the index
        int64 frame = startFrame;
        switch (state->loop_mode) {
                case LOOPING_ALL:
                case LOOPING_RANGE:
                case LOOPING_VISIBLE:
                        frame = startFrame + index;
                        break;
        }
        TRACE("PlaybackManager::_FrameForRangeFrame() done: %" PRId64 "\n", frame);
        return frame;
}


/*!     Given an arbitrary Playlist frame this function returns the next frame within
        the playing range for the supplied playing state. */
int64
PlaybackManager::_NextFrameInRange(PlayingState* state, int64 frame)
{
        int64 newFrame = frame;
        int64 startFrame, endFrame, frameCount;
        _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount);
        if (frame < startFrame || frame > endFrame)
                newFrame = _PlayingStartFrameFor(state);
        else {
                int64 index = _RangeFrameForFrame(state, frame);
                newFrame = _FrameForRangeFrame(state, index);
                if (newFrame > frame && _PlayingDirectionFor(state) == -1)
                        newFrame = _FrameForRangeFrame(state, index - 1);
        }
        return newFrame;
}


void
PlaybackManager::_PushSpeedInfo(SpeedInfo* info)
{
#if SUPPORT_SPEED_CHANGES
        if (info == NULL)
                return;
        // check the last state
        if (SpeedInfo* lastSpeed = _LastSpeedInfo()) {
                // set the activation time
                info->activation_time = TimeForFrame(info->activation_frame);
                // Remove the last state, if it won't be activated.
                if (lastSpeed->activation_frame == info->activation_frame) {
//fprintf(stderr, "  replacing last speed info\n");
                        fSpeeds.RemoveItem(lastSpeed);
                        delete lastSpeed;
                }
        }
        fSpeeds.AddItem(info);
//fprintf(stderr, "  speed info pushed: activation frame: %lld, "
//"activation time: %lld, speed: %f\n", info->activation_frame,
//info->activation_time, info->speed);
        // ...
#endif
}


PlaybackManager::SpeedInfo*
PlaybackManager::_LastSpeedInfo() const
{
#if SUPPORT_SPEED_CHANGES
        return (SpeedInfo*)fSpeeds.ItemAt(fSpeeds.CountItems() - 1);
#else
        return NULL;
#endif
}


PlaybackManager::SpeedInfo*
PlaybackManager::_SpeedInfoAt(int32 index) const
{
#if SUPPORT_SPEED_CHANGES
        return (SpeedInfo*)fSpeeds.ItemAt(index);
#else
        return NULL;
#endif
}


int32
PlaybackManager::_SpeedInfoIndexForFrame(int64 frame) const
{
        int32 index = 0;
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* info;
        while (((info = _SpeedInfoAt(index + 1)))
                   && info->activation_frame <= frame) {
                index++;
        }
//fprintf(stderr, "PlaybackManager::_SpeedInfoIndexForFrame(%lld): %ld\n",
//frame, index);
#endif
        return index;
}


int32
PlaybackManager::_SpeedInfoIndexForTime(bigtime_t time) const
{
        int32 index = 0;
#if SUPPORT_SPEED_CHANGES
        SpeedInfo* info;
        while (((info = _SpeedInfoAt(index + 1)))
                   && info->activation_time <= time) {
                index++;
        }
//fprintf(stderr, "PlaybackManager::_SpeedInfoIndexForTime(%lld): %ld\n",
//time, index);
#endif
        return index;
}


PlaybackManager::SpeedInfo*
PlaybackManager::_SpeedInfoForFrame(int64 frame) const
{
        return _SpeedInfoAt(_SpeedInfoIndexForFrame(frame));
}


PlaybackManager::SpeedInfo*
PlaybackManager::_SpeedInfoForTime(bigtime_t time) const
{
        return _SpeedInfoAt(_SpeedInfoIndexForTime(time));
}


void
PlaybackManager::_UpdateSpeedInfos()
{
#if SUPPORT_SPEED_CHANGES
        int32 firstActive = _SpeedInfoIndexForTime(fPerformanceTime);
        for (int32 i = 0; i < firstActive; i++)
                delete _SpeedInfoAt(i);
        if (firstActive > 0)
                fSpeeds.RemoveItems(0, firstActive);
//fprintf(stderr, "  speed infos 0 - %ld removed\n", firstActive);
#endif
}


/*!     Returns the end time for the last video frame that the video and the
        audio producer both have completely processed. */
bigtime_t
PlaybackManager::_TimeForLastFrame() const
{
        if (fNoAudio)
                return TimeForFrame(FrameForTime(fCurrentVideoTime));

        return TimeForFrame(FrameForTime(min((bigtime_t)fCurrentAudioTime,
                                                                                 (bigtime_t)fCurrentVideoTime)));
}


/*!     Returns the start time for the first video frame for which
        neither the video nor the audio producer have fetched data. */
bigtime_t
PlaybackManager::_TimeForNextFrame() const
{
        return TimeForFrame(NextFrame());
}


void
PlaybackManager::_CheckStopPlaying()
{
//printf("_CheckStopPlaying() - %lld, next: %lld\n", fStopPlayingFrame, NextFrame());
        if (fStopPlayingFrame > 0 && fStopPlayingFrame <= NextFrame()) {
                StopPlaying();
                NotifyStopFrameReached();
        }
}


void
PlaybackManager::_NotifySeekHandledIfNecessary(PlayingState* state)
{
        if (state->is_seek_request) {
                state->is_seek_request = false;
                NotifySeekHandled(state->current_frame);
        }
}