// PlaybackManager.cpp #include #include #include #include #include #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); } }