diff --git a/src/apps/mediaplayer/AbstractLOAdapter.cpp b/src/apps/mediaplayer/AbstractLOAdapter.cpp new file mode 100644 index 0000000000..fca20b7354 --- /dev/null +++ b/src/apps/mediaplayer/AbstractLOAdapter.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + +#include "AbstractLOAdapter.h" + +#include +#include +#include + +// constructor +AbstractLOAdapter::AbstractLOAdapter(BHandler* handler) + : fHandler(handler), + fMessenger(NULL) +{ +} + +// constructor +AbstractLOAdapter::AbstractLOAdapter(const BMessenger& messenger) + : fHandler(NULL), + fMessenger(new BMessenger(messenger)) +{ +} + +// destructor +AbstractLOAdapter::~AbstractLOAdapter() +{ + delete fMessenger; +} + +// DeliverMessage +void +AbstractLOAdapter::DeliverMessage(BMessage* message) +{ + if (fHandler) { + if (BLooper* looper = fHandler->Looper()) + looper->PostMessage(message, fHandler); + } else if (fMessenger) + fMessenger->SendMessage(message); +} + +// DeliverMessage +void +AbstractLOAdapter::DeliverMessage(BMessage& message) +{ + DeliverMessage(&message); +} + +// DeliverMessage +void +AbstractLOAdapter::DeliverMessage(uint32 command) +{ + BMessage message(command); + DeliverMessage(&message); +} + diff --git a/src/apps/mediaplayer/AbstractLOAdapter.h b/src/apps/mediaplayer/AbstractLOAdapter.h new file mode 100644 index 0000000000..6ecd3fb01a --- /dev/null +++ b/src/apps/mediaplayer/AbstractLOAdapter.h @@ -0,0 +1,41 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + +// This class provides some basic functionality for derivation of a +// listener -> observer adapter. +// The derived class should implement constructors similar to the +// ones of this class and pass the respective parameter. +// Each of the listener hook functions should construct a message +// and let it be delivered by DeliverMessage(). + +#ifndef ABSTRACT_LO_ADAPTER_H +#define ABSTRACT_LO_ADAPTER_H + +#include + +class BHandler; +class BLooper; +class BMessage; +class BMessenger; + +class AbstractLOAdapter { + public: + AbstractLOAdapter(BHandler* handler); + AbstractLOAdapter(const BMessenger& messenger); + virtual ~AbstractLOAdapter(); + + void DeliverMessage(BMessage* message); + void DeliverMessage(BMessage& message); + void DeliverMessage(uint32 command); + + private: + BHandler* fHandler; + BMessenger* fMessenger; +}; + +#endif // ABSTRACT_LO_ADAPTER_H diff --git a/src/apps/mediaplayer/Controller.cpp b/src/apps/mediaplayer/Controller.cpp index 6104dda6bb..39bc9a9981 100644 --- a/src/apps/mediaplayer/Controller.cpp +++ b/src/apps/mediaplayer/Controller.cpp @@ -2,6 +2,7 @@ * Controller.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,6 +18,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ +#include "Controller.h" + #include #include #include @@ -27,10 +30,16 @@ #include #include -#include "Controller.h" #include "ControllerView.h" -#include "VideoView.h" +#include "PlaybackState.h" #include "SoundOutput.h" +#include "VideoView.h" + +// suppliers +#include "AudioSupplier.h" +#include "MediaTrackAudioSupplier.h" +#include "MediaTrackVideoSupplier.h" +#include "VideoSupplier.h" #define AUDIO_PLAY_PRIORITY 110 @@ -49,20 +58,44 @@ HandleError(const char *text, status_t err) } +// #pragma mark - + + +Controller::Listener::Listener() {} +Controller::Listener::~Listener() {} +void Controller::Listener::FileFinished() {} +void Controller::Listener::FileChanged() {} +void Controller::Listener::VideoTrackChanged(int32) {} +void Controller::Listener::AudioTrackChanged(int32) {} +void Controller::Listener::VideoStatsChanged() {} +void Controller::Listener::AudioStatsChanged() {} +void Controller::Listener::PlaybackStateChanged(uint32) {} +void Controller::Listener::PositionChanged(float) {} +void Controller::Listener::VolumeChanged(float) {} + +// #pragma mark - + + Controller::Controller() : fVideoView(NULL) - , fControllerView(NULL) , fName() , fPaused(false) , fStopped(true) + , fVolume(1.0) + , fMediaFile(0) - , fAudioTrack(0) - , fVideoTrack(0) - , fAudioTrackLock("audio track lock") - , fVideoTrackLock("video track lock") +,fAudioTrack(0) +,fVideoTrack(0) + , fDataLock("controller data lock") + + , fVideoSupplier(NULL) + , fAudioSupplier(NULL) + + , fVideoSupplierLock("video supplier lock") + , fAudioSupplierLock("audio supplier lock") + , fAudioTrackList(new BList) , fVideoTrackList(new BList) - , fPosition(0) , fAudioDecodeSem(-1) , fVideoDecodeSem(-1) , fAudioPlaySem(-1) @@ -75,6 +108,7 @@ Controller::Controller() , fSeekAudio(false) , fSeekVideo(false) , fSeekPosition(0) + , fPosition(0) , fDuration(0) , fAudioBufferCount(MAX_AUDIO_BUFFERS) , fAudioBufferReadIndex(0) @@ -86,77 +120,104 @@ Controller::Controller() , fTimeSourceSysTime(0) , fTimeSourcePerfTime(0) , fAutoplay(true) + , fPauseAtEndOfStream(false) + , fSeekToStartAfterPause(false) , fCurrentBitmap(NULL) + , fBitmapLock("bitmap lock") + , fListeners(4) { for (int i = 0; i < MAX_AUDIO_BUFFERS; i++) { fAudioBuffer[i].bitmap = NULL; fAudioBuffer[i].buffer = NULL; fAudioBuffer[i].sizeMax = 0; + fAudioBuffer[i].formatChanged = false; + fAudioBuffer[i].endOfStream = false; } for (int i = 0; i < MAX_VIDEO_BUFFERS; i++) { fVideoBuffer[i].bitmap = NULL; fVideoBuffer[i].buffer = NULL; fVideoBuffer[i].sizeMax = 0; + fVideoBuffer[i].formatChanged = false; + fVideoBuffer[i].endOfStream = false; } fStopped = fAutoplay ? false : true; + _StartThreads(); } Controller::~Controller() { - StopThreads(); - + _StopThreads(); + if (fMediaFile) fMediaFile->ReleaseAllTracks(); delete fMediaFile; delete fAudioTrackList; delete fVideoTrackList; + + delete fSoundOutput; +} + + +// #pragma mark - + + +bool +Controller::Lock() +{ + return fDataLock.Lock(); +} + + +status_t +Controller::LockWithTimeout(bigtime_t timeout) +{ + return fDataLock.LockWithTimeout(timeout); } void -Controller::SetVideoView(VideoView *view) +Controller::Unlock() { - fVideoView = view; - fVideoView->SetController(this); + return fDataLock.Unlock(); } -void -Controller::SetControllerView(ControllerView *view) -{ - fControllerView = view; -} +// #pragma mark - status_t Controller::SetTo(const entry_ref &ref) { - StopThreads(); - - UpdatePosition(0); - - BAutolock al(fAudioTrackLock); - BAutolock vl(fVideoTrackLock); + BAutolock dl(fDataLock); + BAutolock al(fVideoSupplierLock); + BAutolock vl(fAudioSupplierLock); fAudioTrackList->MakeEmpty(); fVideoTrackList->MakeEmpty(); if (fMediaFile) fMediaFile->ReleaseAllTracks(); delete fMediaFile; - fAudioTrack = 0; - fVideoTrack = 0; - fMediaFile = 0; - fPosition = 0; - fSeekAudio = false; - fSeekVideo = false; + fMediaFile = NULL; + delete fVideoSupplier; + fVideoSupplier = NULL; + delete fAudioSupplier; + fAudioSupplier = NULL; + + fSeekAudio = true; + fSeekVideo = true; + fSeekPosition = 0; fPaused = false; fStopped = fAutoplay ? false : true; + fPauseAtEndOfStream = false; + fSeekToStartAfterPause = false; fDuration = 0; fName = ref.name; + _UpdatePosition(0); + status_t err; BMediaFile *mf = new BMediaFile(&ref); @@ -164,6 +225,7 @@ Controller::SetTo(const entry_ref &ref) if (err != B_OK) { printf("Controller::SetTo: initcheck failed\n"); delete mf; + _NotifyFileChanged(); return err; } @@ -171,6 +233,7 @@ Controller::SetTo(const entry_ref &ref) if (trackcount <= 0) { printf("Controller::SetTo: trackcount %d\n", trackcount); delete mf; + _NotifyFileChanged(); return B_MEDIA_NO_HANDLER; } @@ -197,6 +260,7 @@ Controller::SetTo(const entry_ref &ref) if (AudioTrackCount() == 0 && VideoTrackCount() == 0) { printf("Controller::SetTo: no audio or video tracks found\n"); delete mf; + _NotifyFileChanged(); return B_MEDIA_NO_HANDLER; } @@ -206,9 +270,10 @@ Controller::SetTo(const entry_ref &ref) SelectAudioTrack(0); SelectVideoTrack(0); - - StartThreads(); - + + _NotifyFileChanged(); + _NotifyPlaybackStateChanged(); + return B_OK; } @@ -216,52 +281,57 @@ Controller::SetTo(const entry_ref &ref) void Controller::GetSize(int *width, int *height) { -/* - media_format fmt; - buffer->mediaFormat.type = B_MEDIA_RAW_VIDEO; - buffer->mediaFormat.u.raw_video = media_raw_video_format::wildcard; - buffer->mediaFormat.u.raw_video.display.format = IsOverlayActive() ? B_YCbCr422 : B_RGB32; - if (fVideoTrack || B_OK != fVideoTrack->DecodedFormat(&fmt)) { -*/ + // you need to hole the data lock + + media_format format; + format.type = B_MEDIA_RAW_VIDEO; + format.u.raw_video = media_raw_video_format::wildcard; + format.u.raw_video.display.format = IsOverlayActive() ? B_YCbCr422 : B_RGB32; + + if (!fVideoTrack || B_OK != fVideoTrack->DecodedFormat(&format)) { *height = 480; *width = 640; -/* } else { - *height = fmt.u.raw_video.display.line_count; - *width = fmt.u.raw_video.display.line_width; + *height = format.u.raw_video.display.line_count; + *width = format.u.raw_video.display.line_width; } -*/ -} - - -int -Controller::VideoTrackCount() -{ - return fVideoTrackList->CountItems(); } int Controller::AudioTrackCount() { + // you need to hole the data lock return fAudioTrackList->CountItems(); } +int +Controller::VideoTrackCount() +{ + // you need to hole the data lock + return fVideoTrackList->CountItems(); +} + + status_t Controller::SelectAudioTrack(int n) { - BAutolock al(fAudioTrackLock); + BAutolock _(fAudioSupplierLock); - BMediaTrack *t = (BMediaTrack *)fAudioTrackList->ItemAt(n); - if (!t) + BMediaTrack* track = (BMediaTrack *)fAudioTrackList->ItemAt(n); + if (!track) return B_ERROR; - fAudioTrack = t; - bigtime_t a = fAudioTrack ? fAudioTrack->Duration() : 0; - bigtime_t v = fVideoTrack ? fVideoTrack->Duration() : 0; +fAudioTrack = track; + delete fAudioSupplier; + fAudioSupplier = new MediaTrackAudioSupplier(track); + + bigtime_t a = fAudioSupplier->Duration(); + bigtime_t v = fVideoSupplier ? fVideoSupplier->Duration() : 0;; fDuration = max_c(a, v); - + + _NotifyAudioTrackChanged(n); return B_OK; } @@ -269,60 +339,48 @@ Controller::SelectAudioTrack(int n) status_t Controller::SelectVideoTrack(int n) { - BAutolock vl(fVideoTrackLock); + BAutolock _(fVideoSupplierLock); - BMediaTrack *t = (BMediaTrack *)fVideoTrackList->ItemAt(n); - if (!t) + BMediaTrack* track = (BMediaTrack *)fVideoTrackList->ItemAt(n); + if (!track) return B_ERROR; - fVideoTrack = t; - bigtime_t a = fAudioTrack ? fAudioTrack->Duration() : 0; - bigtime_t v = fVideoTrack ? fVideoTrack->Duration() : 0; +fVideoTrack = track; + delete fVideoSupplier; + fVideoSupplier = new MediaTrackVideoSupplier(track, + IsOverlayActive() ? B_YCbCr422 : B_RGB32); + + bigtime_t a = fAudioSupplier ? fAudioSupplier->Duration() : 0; + bigtime_t v = fVideoSupplier->Duration(); fDuration = max_c(a, v); + _NotifyVideoTrackChanged(n); return B_OK; } -bigtime_t -Controller::Duration() -{ - return fDuration; -} - - -bigtime_t -Controller::Position() -{ - return fPosition; -} - - -status_t -Controller::Seek(bigtime_t pos) -{ - return B_OK; -} - - -void -Controller::VolumeUp() -{ -} - - -void -Controller::VolumeDown() -{ -} +// #pragma mark - void Controller::SetVolume(float value) { printf("Controller::SetVolume %.4f\n", value); - if (fSoundOutput) // hack... - fSoundOutput->SetVolume(value); + if (Lock()) { + fVolume = value; + if (fSoundOutput) + fSoundOutput->SetVolume(fVolume); + Unlock(); + } +} + + +float +Controller::Volume() const +{ + BAutolock _(fDataLock); + + return fVolume; } @@ -330,23 +388,137 @@ void Controller::SetPosition(float value) { printf("Controller::SetPosition %.4f\n", value); + + BAutolock _(fDataLock); + fSeekPosition = bigtime_t(value * Duration()); fSeekAudio = true; fSeekVideo = true; + + fSeekToStartAfterPause = false; } void -Controller::UpdateVolume(float value) +Controller::Stop() { - fControllerView->SetVolume(value); + printf("Controller::Stop\n"); + + BAutolock _(fDataLock); + + if (fStopped) + return; + + fPaused = false; + fStopped = true; + + fSeekAudio = true; + fSeekVideo = true; + fSeekPosition = 0; + + _NotifyPlaybackStateChanged(); + _UpdatePosition(0, false, true); } void -Controller::UpdatePosition(float value) +Controller::Play() { - fControllerView->SetPosition(value); + printf("Controller::Play\n"); + + BAutolock _(fDataLock); + + if (!fPaused && !fStopped) + return; + + fStopped = false; + fPaused = false; + + if (fSeekToStartAfterPause) { +printf("seeking to start after pause\n"); + SetPosition(0); + fSeekToStartAfterPause = false; + } + + _NotifyPlaybackStateChanged(); +} + + +void +Controller::Pause() +{ + printf("Controller::Pause\n"); + + BAutolock _(fDataLock); + + if (fStopped || fPaused) + return; + + fPaused = true; + + _NotifyPlaybackStateChanged(); +} + + +bool +Controller::IsPaused() const +{ + BAutolock _(fDataLock); + + return fPaused; +} + + +bool +Controller::IsStopped() const +{ + BAutolock _(fDataLock); + + return fStopped; +} + + +uint32 +Controller::PlaybackState() const +{ + BAutolock _(fDataLock); + + if (fStopped) + return PLAYBACK_STATE_STOPPED; + if (fPaused) + return PLAYBACK_STATE_PAUSED; + return PLAYBACK_STATE_PLAYING; +} + + +bigtime_t +Controller::Duration() +{ + BAutolock _(fDataLock); + + return fDuration; +} + + +bigtime_t +Controller::Position() +{ + BAutolock _(fDataLock); + + return fPosition; +} + + +// #pragma mark - + + +void +Controller::SetVideoView(VideoView *view) +{ + BAutolock _(fDataLock); + + fVideoView = view; + fVideoView->SetController(this); } @@ -358,15 +530,17 @@ Controller::IsOverlayActive() } -void +bool Controller::LockBitmap() { + return fBitmapLock.Lock(); } void Controller::UnlockBitmap() { + fBitmapLock.Unlock(); } @@ -377,150 +551,125 @@ Controller::Bitmap() } -void -Controller::Stop() -{ - printf("Controller::Stop\n"); - - if (fStopped) - return; - - fPaused = false; - fStopped = true; - - // böse... - snooze(30000); - fSeekAudio = true; - fSeekVideo = true; - fSeekPosition = 0; - snooze(30000); - UpdatePosition(0); -} - - -void -Controller::Play() -{ - printf("Controller::Play\n"); - - if (!fPaused && !fStopped) - return; - - fStopped = - fPaused = false; -} - - -void -Controller::Pause() -{ - printf("Controller::Pause\n"); - - if (fStopped || fPaused) - return; - - fPaused = true; -} +// #pragma mark - bool -Controller::IsPaused() +Controller::AddListener(Listener* listener) { - return fPaused; -} + BAutolock _(fDataLock); - -bool -Controller::IsStopped() -{ - return fStopped; + if (listener && !fListeners.HasItem(listener)) + return fListeners.AddItem(listener); + return false; } void -Controller::AudioDecodeThread() +Controller::RemoveListener(Listener* listener) { - BMediaTrack *lastTrack = 0; + BAutolock _(fDataLock); + + fListeners.RemoveItem(listener); +} + + +// #pragma mark - + + +void +Controller::_AudioDecodeThread() +{ + AudioSupplier* lastSupplier = NULL; size_t bufferSize = 0; size_t frameSize = 0; bool decodeError = false; - status_t status; printf("audio decode start\n"); - if (fAudioTrack == 0) { - return; - } - while (acquire_sem(fAudioDecodeSem) == B_OK) { //printf("audio decoding..\n"); buffer_info *buffer = &fAudioBuffer[fAudioBufferWriteIndex]; fAudioBufferWriteIndex = (fAudioBufferWriteIndex + 1) % fAudioBufferCount; - BAutolock lock(fAudioTrackLock); - if (lastTrack != fAudioTrack) { -//printf("audio track changed\n"); - lastTrack = fAudioTrack; - buffer->formatChanged = true; - buffer->mediaFormat.type = B_MEDIA_RAW_AUDIO; - buffer->mediaFormat.u.raw_audio = media_multi_audio_format::wildcard; - #ifdef __HAIKU__ - buffer->mediaFormat.u.raw_audio.format = media_multi_audio_format::B_AUDIO_FLOAT; - #endif - status = fAudioTrack->DecodedFormat(&buffer->mediaFormat); - if (status != B_OK) { - printf("audio decoded format status %08lx %s\n", status, strerror(status)); - return; - } - bufferSize = buffer->mediaFormat.u.raw_audio.buffer_size; - frameSize = (buffer->mediaFormat.u.raw_audio.format & 0xf) * buffer->mediaFormat.u.raw_audio.channel_count; - } else { - buffer->formatChanged = false; + + if (!fAudioSupplierLock.Lock()) + return; + + buffer->formatChanged = lastSupplier != fAudioSupplier; + lastSupplier = fAudioSupplier; + + if (!lastSupplier) { + // no audio supplier + buffer->sizeUsed = 0; + buffer->startTime = 0; + + fAudioSupplierLock.Unlock(); + snooze(10000); + release_sem(fAudioPlaySem); + continue; } + + if (buffer->formatChanged) { +printf("audio format changed\n"); + buffer->mediaFormat = lastSupplier->Format(); + bufferSize = buffer->mediaFormat.u.raw_audio.buffer_size; + frameSize = (buffer->mediaFormat.u.raw_audio.format & 0xf) + * buffer->mediaFormat.u.raw_audio.channel_count; + } + if (buffer->sizeMax < bufferSize) { - delete buffer->buffer; + delete[] buffer->buffer; buffer->buffer = new char [bufferSize]; - buffer->sizeMax = bufferSize;; + buffer->sizeMax = bufferSize; } if (fSeekAudio) { bigtime_t pos = fSeekPosition; - fAudioTrack->SeekToTime(&pos); + lastSupplier->SeekToTime(&pos); fSeekAudio = false; decodeError = false; } int64 frames; - media_header mh; if (!decodeError) { - decodeError = B_OK != fAudioTrack->ReadFrames(buffer->buffer, &frames, &mh); + buffer->endOfStream = false; + status_t ret = lastSupplier->ReadFrames(buffer->buffer, + &frames, &buffer->startTime); + decodeError = B_OK != ret; +if (ret != B_OK) +printf("audio decode error: %s\n", strerror(ret)); + if (ret == B_LAST_BUFFER_ERROR) { + _EndOfStreamReached(); + buffer->endOfStream = true; + } } if (!decodeError) { buffer->sizeUsed = frames * frameSize; - buffer->startTime = mh.start_time; } else { buffer->sizeUsed = 0; buffer->startTime = 0; + + fAudioSupplierLock.Unlock(); + snooze(10000); + release_sem(fAudioPlaySem); + continue; } - + + fAudioSupplierLock.Unlock(); + release_sem(fAudioPlaySem); } } void -Controller::AudioPlayThread() +Controller::_AudioPlayThread() { - SoundOutput *output = NULL; bigtime_t bufferDuration = 0; + bigtime_t audioLatency = 0; status_t status; - printf("audio play start\n"); - if (fAudioTrack == 0) { - fTimeSourceSysTime = 0; - fTimeSourcePerfTime = 0; - return; - } while (acquire_sem(fAudioPlaySem) == B_OK) { //printf("audio playing..\n"); @@ -534,36 +683,67 @@ printf("audio play start\n"); break; goto wait; } - if (buffer->formatChanged) { -//printf("format changed..\n"); - delete output; - output = new SoundOutput(fName.String(), buffer->mediaFormat.u.raw_audio); - bufferDuration = bigtime_t(1000000.0 * (buffer->mediaFormat.u.raw_audio.buffer_size / (buffer->mediaFormat.u.raw_audio.channel_count * (buffer->mediaFormat.u.raw_audio.format & 0xf))) / buffer->mediaFormat.u.raw_audio.frame_rate); - printf("audio format changed, new buffer duration %Ld\n", bufferDuration); - fSoundOutput = output; // hack... + + if (!fDataLock.Lock()) + return; + + // TODO: fix performance time update (in case no audio) + if (fTimeSourceLock.Lock()) { + fTimeSourceSysTime = system_time() + audioLatency - bufferDuration; + fTimeSourceLock.Unlock(); } - if (output) { - if (buffer->sizeUsed) - output->Play(buffer->buffer, buffer->sizeUsed); - BAutolock lock(fTimeSourceLock); - fTimeSourceSysTime = system_time() + output->Latency() - bufferDuration; - fTimeSourcePerfTime = buffer->startTime; -// printf("timesource: sys: %Ld perf: %Ld\n", fTimeSourceSysTime, fTimeSourcePerfTime); - UpdatePosition(buffer->startTime / (float)Duration()); - } else { - debugger("kein SoundOutput"); +// printf("timesource: sys: %Ld perf: %Ld\n", fTimeSourceSysTime, +// fTimeSourcePerfTime); + + if (buffer->sizeUsed == 0) { + bool pause = fPauseAtEndOfStream && buffer->endOfStream; + fDataLock.Unlock(); + release_sem(fAudioDecodeSem); + if (pause) { + fPauseAtEndOfStream = false; + fSeekToStartAfterPause = true; + Pause(); + } else + snooze(25000); + continue; } + + if (!fSoundOutput || buffer->formatChanged) { + if (!fSoundOutput + || !(fSoundOutput->Format() == buffer->mediaFormat.u.raw_audio)) { + delete fSoundOutput; + fSoundOutput = new (nothrow) SoundOutput(fName.String(), + buffer->mediaFormat.u.raw_audio); + fSoundOutput->SetVolume(fVolume); +bufferDuration = bigtime_t(1000000.0 + * (buffer->mediaFormat.u.raw_audio.buffer_size + / (buffer->mediaFormat.u.raw_audio.channel_count + * (buffer->mediaFormat.u.raw_audio.format & 0xf))) + / buffer->mediaFormat.u.raw_audio.frame_rate); +printf("audio format changed, new buffer duration %Ld\n", bufferDuration); + } + } + + if (fSoundOutput) { + fSoundOutput->Play(buffer->buffer, buffer->sizeUsed); + audioLatency = fSoundOutput->Latency(); + _UpdatePosition(buffer->startTime); + } + + fDataLock.Unlock(); + release_sem(fAudioDecodeSem); } - delete output; - fSoundOutput = NULL; // hack... } +// #pragma mark - + + void -Controller::VideoDecodeThread() +Controller::_VideoDecodeThread() { - BMediaTrack *lastTrack = 0; + VideoSupplier* lastSupplier = NULL; size_t bufferSize = 0; size_t bytePerRow = 0; int lineWidth = 0; @@ -572,84 +752,108 @@ Controller::VideoDecodeThread() status_t status; printf("video decode start\n"); - if (fVideoTrack == 0) { - return; - } while (acquire_sem(fVideoDecodeSem) == B_OK) { buffer_info *buffer = &fVideoBuffer[fVideoBufferWriteIndex]; fVideoBufferWriteIndex = (fVideoBufferWriteIndex + 1) % fVideoBufferCount; - BAutolock lock(fVideoTrackLock); - if (lastTrack != fVideoTrack) { + + if (!fVideoSupplierLock.Lock()) + return; + + buffer->formatChanged = lastSupplier != fVideoSupplier; + lastSupplier = fVideoSupplier; + + if (!lastSupplier) { + // no video supplier + buffer->sizeUsed = 0; + buffer->startTime = 0; + + fVideoSupplierLock.Unlock(); + snooze(10000); + release_sem(fVideoDecodeSem); + continue; + } + + + if (buffer->formatChanged) { //printf("Video track changed\n"); - lastTrack = fVideoTrack; - buffer->formatChanged = true; - buffer->mediaFormat.type = B_MEDIA_RAW_VIDEO; - buffer->mediaFormat.u.raw_video = media_raw_video_format::wildcard; - buffer->mediaFormat.u.raw_video.display.format = IsOverlayActive() ? B_YCbCr422 : B_RGB32; - status = fVideoTrack->DecodedFormat(&buffer->mediaFormat); - if (status != B_OK) { - printf("video decoded format status %08lx %s\n", status, strerror(status)); - return; - } + buffer->mediaFormat = lastSupplier->Format(); + bytePerRow = buffer->mediaFormat.u.raw_video.display.bytes_per_row; lineWidth = buffer->mediaFormat.u.raw_video.display.line_width; lineCount = buffer->mediaFormat.u.raw_video.display.line_count; bufferSize = lineCount * bytePerRow; - } else { - buffer->formatChanged = false; } + if (buffer->sizeMax != bufferSize) { + LockBitmap(); + BRect r(0, 0, lineWidth - 1, lineCount - 1); + if (buffer->bitmap == fCurrentBitmap) + fCurrentBitmap = NULL; delete buffer->bitmap; - printf("allocating bitmap %d %d %ld\n", lineWidth, lineCount, bytePerRow); - if (IsOverlayActive()) - buffer->bitmap = new BBitmap(r, B_BITMAP_WILL_OVERLAY | (fVideoBufferWriteIndex == 1) ? B_BITMAP_RESERVE_OVERLAY_CHANNEL : 0, IsOverlayActive() ? B_YCbCr422 : B_RGB32, bytePerRow); - else +printf("allocating bitmap #%ld %d %d %ld\n", + fVideoBufferWriteIndex, lineWidth, lineCount, bytePerRow); + if (buffer->mediaFormat.u.raw_video.display.format == B_YCbCr422) { + buffer->bitmap = new BBitmap(r, B_BITMAP_WILL_OVERLAY + | (fVideoBufferWriteIndex == 0) ? + B_BITMAP_RESERVE_OVERLAY_CHANNEL : 0, + B_YCbCr422, bytePerRow); + } else { buffer->bitmap = new BBitmap(r, 0, B_RGB32, bytePerRow); + } status = buffer->bitmap->InitCheck(); if (status != B_OK) { - printf("video decoded format status %08lx %s\n", status, strerror(status)); + printf("bitmap init status %08lx %s\n", status, strerror(status)); return; } buffer->buffer = (char *)buffer->bitmap->Bits(); buffer->sizeMax = bufferSize; + + UnlockBitmap(); } if (fSeekVideo) { bigtime_t pos = fSeekPosition; - fVideoTrack->SeekToTime(&pos); + lastSupplier->SeekToTime(&pos); fSeekVideo = false; decodeError = false; } - int64 frames; - media_header mh; if (!decodeError) { // printf("reading video frame...\n"); - decodeError = B_OK != fVideoTrack->ReadFrames(buffer->buffer, &frames, &mh); + status_t ret = lastSupplier->ReadFrame(buffer->buffer, + &buffer->startTime); + decodeError = B_OK != ret; + if (ret == B_LAST_BUFFER_ERROR) { + buffer->endOfStream = true; + _EndOfStreamReached(true); + } } if (!decodeError) { buffer->sizeUsed = buffer->sizeMax; - buffer->startTime = mh.start_time; } else { buffer->sizeUsed = 0; buffer->startTime = 0; + + fVideoSupplierLock.Unlock(); + snooze(10000); + release_sem(fVideoPlaySem); + continue; } - + + fVideoSupplierLock.Unlock(); + release_sem(fVideoPlaySem); } } void -Controller::VideoPlayThread() +Controller::_VideoPlayThread() { status_t status; printf("video decode start\n"); - if (fVideoTrack == 0) { - return; - } while (acquire_sem(fVideoPlaySem) == B_OK) { @@ -692,9 +896,36 @@ printf("video decode start\n"); status = acquire_sem_etc(fThreadWaitSem, 1, B_ABSOLUTE_TIMEOUT, waituntil); if (status != B_TIMED_OUT) break; - - fCurrentBitmap = buffer->bitmap; - fVideoView->DrawFrame(); + + if (!fDataLock.Lock()) + return; + + if (fVideoView) { + if (buffer->sizeUsed == buffer->sizeMax) { + LockBitmap(); + fCurrentBitmap = buffer->bitmap; + fVideoView->DrawFrame(); + UnlockBitmap(); + + _UpdatePosition(buffer->startTime, true); + } else { + bool pause = fPauseAtEndOfStream && buffer->endOfStream; + fDataLock.Unlock(); + + release_sem(fVideoDecodeSem); + + if (pause) { + fPauseAtEndOfStream = false; + fSeekToStartAfterPause = true; + Pause(); + } else + snooze(25000); + continue; + } + } else + debugger("fVideoView == NULL"); + + fDataLock.Unlock(); // snooze(60000); release_sem(fVideoDecodeSem); @@ -706,8 +937,11 @@ printf("video decode start\n"); } +// #pragma mark - + + void -Controller::StartThreads() +Controller::_StartThreads() { if (fAudioDecodeSem > 0) return; @@ -721,10 +955,10 @@ Controller::StartThreads() fAudioPlaySem = create_sem(0, "audio play sem"); fVideoPlaySem = create_sem(0, "video play sem"); fThreadWaitSem = create_sem(0, "thread wait sem"); - fAudioDecodeThread = spawn_thread(audio_decode_thread, "audio decode", AUDIO_DECODE_PRIORITY, this); - fVideoDecodeThread = spawn_thread(video_decode_thread, "video decode", VIDEO_DECODE_PRIORITY, this); - fAudioPlayThread = spawn_thread(audio_play_thread, "audio play", AUDIO_PLAY_PRIORITY, this); - fVideoPlayThread = spawn_thread(video_play_thread, "video play", VIDEO_PLAY_PRIORITY, this); + fAudioDecodeThread = spawn_thread(_AudioDecodeThreadEntry, "audio decode", AUDIO_DECODE_PRIORITY, this); + fVideoDecodeThread = spawn_thread(_VideoDecodeThreadEntry, "video decode", VIDEO_DECODE_PRIORITY, this); + fAudioPlayThread = spawn_thread(_AudioPlayThreadEntry, "audio play", AUDIO_PLAY_PRIORITY, this); + fVideoPlayThread = spawn_thread(_VideoPlayThreadEntry, "video play", VIDEO_PLAY_PRIORITY, this); resume_thread(fAudioDecodeThread); resume_thread(fVideoDecodeThread); resume_thread(fAudioPlayThread); @@ -733,7 +967,7 @@ Controller::StartThreads() void -Controller::StopThreads() +Controller::_StopThreads() { if (fAudioDecodeSem < 0) return; @@ -760,33 +994,191 @@ Controller::StopThreads() fVideoPlaySem = -1; } -int32 -Controller::audio_decode_thread(void *self) + +// #pragma mark - + + +void +Controller::_EndOfStreamReached(bool isVideo) { - static_cast(self)->AudioDecodeThread(); + // you need to hold the fDataLock + + // prefer end of audio stream over end of video stream + if (isVideo && fAudioSupplier) + return; + + // NOTE: executed in audio/video decode thread + if (!fStopped) { + fPauseAtEndOfStream = true; + _NotifyFileFinished(); + } +} + + +void +Controller::_UpdatePosition(bigtime_t position, bool isVideoPosition, bool force) +{ + // you need to hold the fDataLock + + if (!force && fStopped) + return; + + // prefer audio position over video position + if (isVideoPosition && fAudioSupplier) + return; + + BAutolock _(fTimeSourceLock); + fTimeSourcePerfTime = position; + + fPosition = position; + float scale = fDuration > 0 ? (float)fPosition / fDuration : 0.0; + _NotifyPositionChanged(scale); +} + + +// #pragma mark - + + +int32 +Controller::_AudioDecodeThreadEntry(void *self) +{ + static_cast(self)->_AudioDecodeThread(); return 0; } int32 -Controller::video_decode_thread(void *self) +Controller::_VideoDecodeThreadEntry(void *self) { - static_cast(self)->VideoDecodeThread(); + static_cast(self)->_VideoDecodeThread(); return 0; } int32 -Controller::audio_play_thread(void *self) +Controller::_AudioPlayThreadEntry(void *self) { - static_cast(self)->AudioPlayThread(); + static_cast(self)->_AudioPlayThread(); return 0; } int32 -Controller::video_play_thread(void *self) +Controller::_VideoPlayThreadEntry(void *self) { - static_cast(self)->VideoPlayThread(); + static_cast(self)->_VideoPlayThread(); return 0; } + + +// #pragma mark - + + +void +Controller::_NotifyFileChanged() +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->FileChanged(); + } +} + + +void +Controller::_NotifyFileFinished() +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->FileFinished(); + } +} + + +void +Controller::_NotifyVideoTrackChanged(int32 index) +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->VideoTrackChanged(index); + } +} + + +void +Controller::_NotifyAudioTrackChanged(int32 index) +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->AudioTrackChanged(index); + } +} + + +void +Controller::_NotifyVideoStatsChanged() +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->VideoStatsChanged(); + } +} + + +void +Controller::_NotifyAudioStatsChanged() +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->AudioStatsChanged(); + } +} + + +void +Controller::_NotifyPlaybackStateChanged() +{ + uint32 state = PlaybackState(); + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->PlaybackStateChanged(state); + } +} + + +void +Controller::_NotifyPositionChanged(float position) +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->PositionChanged(position); + } +} + + +void +Controller::_NotifyVolumeChanged(float volume) +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->VolumeChanged(volume); + } +} + diff --git a/src/apps/mediaplayer/Controller.h b/src/apps/mediaplayer/Controller.h index 9774680b41..03aaecf8c1 100644 --- a/src/apps/mediaplayer/Controller.h +++ b/src/apps/mediaplayer/Controller.h @@ -2,6 +2,7 @@ * Controller.h - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,22 +23,47 @@ #include #include +#include #include #include +class AudioSupplier; class BBitmap; class BMediaFile; class BMediaTrack; class SoundOutput; +class VideoSupplier; class VideoView; -class ControllerView; -class Controller -{ +class Controller { public: + + class Listener { + public: + Listener(); + virtual ~Listener(); + + virtual void FileFinished(); + virtual void FileChanged(); + + virtual void VideoTrackChanged(int32 index); + virtual void AudioTrackChanged(int32 index); + + virtual void VideoStatsChanged(); + virtual void AudioStatsChanged(); + + virtual void PlaybackStateChanged(uint32 state); + virtual void PositionChanged(float position); + virtual void VolumeChanged(float volume); + }; + Controller(); virtual ~Controller(); - + + bool Lock(); + status_t LockWithTimeout(bigtime_t timeout); + void Unlock(); + status_t SetTo(const entry_ref &ref); void GetSize(int *width, int *height); @@ -47,51 +73,67 @@ public: status_t SelectAudioTrack(int n); status_t SelectVideoTrack(int n); - bigtime_t Duration(); - bigtime_t Position(); - - status_t Seek(bigtime_t pos); - void Stop(); void Play(); void Pause(); - bool IsPaused(); - bool IsStopped(); + bool IsPaused() const; + bool IsStopped() const; + uint32 PlaybackState() const; + + bigtime_t Duration(); + bigtime_t Position(); + + void SetVolume(float value); + float Volume() const; + void SetPosition(float value); + + // video view void SetVideoView(VideoView *view); - void SetControllerView(ControllerView *view); bool IsOverlayActive(); - void LockBitmap(); + bool LockBitmap(); void UnlockBitmap(); BBitmap * Bitmap(); - void VolumeUp(); - void VolumeDown(); - - void SetVolume(float value); - - void SetPosition(float value); - - void UpdateVolume(float value); - void UpdatePosition(float value); + // notification support + bool AddListener(Listener* listener); + void RemoveListener(Listener* listener); private: - static int32 audio_decode_thread(void *self); - static int32 video_decode_thread(void *self); - static int32 audio_play_thread(void *self); - static int32 video_play_thread(void *self); + void _AudioDecodeThread(); + void _AudioPlayThread(); + + void _VideoDecodeThread(); + void _VideoPlayThread(); + + void _StartThreads(); + void _StopThreads(); + + void _EndOfStreamReached(bool isVideo = false); + void _UpdatePosition(bigtime_t position, + bool isVideoPosition = false, + bool force = false); + + static int32 _VideoDecodeThreadEntry(void *self); + static int32 _VideoPlayThreadEntry(void *self); + static int32 _AudioDecodeThreadEntry(void *self); + static int32 _AudioPlayThreadEntry(void *self); - void AudioDecodeThread(); - void VideoDecodeThread(); - void AudioPlayThread(); - void VideoPlayThread(); - - void StartThreads(); - void StopThreads(); - private: + void _NotifyFileChanged(); + void _NotifyFileFinished(); + void _NotifyVideoTrackChanged(int32 index); + void _NotifyAudioTrackChanged(int32 index); + + void _NotifyVideoStatsChanged(); + void _NotifyAudioStatsChanged(); + + void _NotifyPlaybackStateChanged(); + void _NotifyPositionChanged(float position); + void _NotifyVolumeChanged(float volume); + friend class InfoWin; enum { @@ -106,24 +148,29 @@ private: size_t sizeMax; bigtime_t startTime; bool formatChanged; + bool endOfStream; media_format mediaFormat; }; VideoView * fVideoView; - ControllerView * fControllerView; BString fName; volatile bool fPaused; volatile bool fStopped; + float fVolume; + BMediaFile * fMediaFile; - BMediaTrack * fAudioTrack; - BMediaTrack * fVideoTrack; - - BLocker fAudioTrackLock; - BLocker fVideoTrackLock; - +BMediaTrack * fAudioTrack; +BMediaTrack * fVideoTrack; + mutable BLocker fDataLock; + + VideoSupplier* fVideoSupplier; + AudioSupplier* fAudioSupplier; + + BLocker fVideoSupplierLock; + BLocker fAudioSupplierLock; + BList * fAudioTrackList; BList * fVideoTrackList; - bigtime_t fPosition; media_format fAudioFormat; media_format fVideoFormat; @@ -141,6 +188,7 @@ private: volatile bool fSeekAudio; volatile bool fSeekVideo; volatile bigtime_t fSeekPosition; + bigtime_t fPosition; bigtime_t fDuration; int32 fAudioBufferCount; @@ -157,9 +205,13 @@ private: bigtime_t fTimeSourceSysTime; bigtime_t fTimeSourcePerfTime; bool fAutoplay; + volatile bool fPauseAtEndOfStream; + volatile bool fSeekToStartAfterPause; BBitmap * fCurrentBitmap; + BLocker fBitmapLock; + BList fListeners; }; diff --git a/src/apps/mediaplayer/ControllerObserver.cpp b/src/apps/mediaplayer/ControllerObserver.cpp new file mode 100644 index 0000000000..f320d32f02 --- /dev/null +++ b/src/apps/mediaplayer/ControllerObserver.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "ControllerObserver.h" + +#include + + +ControllerObserver::ControllerObserver(BHandler* target, uint32 observeFlags) + : Controller::Listener() + , AbstractLOAdapter(target) + , fObserveFlags(observeFlags) +{ +} + + +ControllerObserver::~ControllerObserver() +{ +} + + +void +ControllerObserver::FileFinished() +{ + if (!(fObserveFlags & OBSERVE_FILE_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_FILE_FINISHED); + + DeliverMessage(message); +} + + +void +ControllerObserver::FileChanged() +{ + if (!(fObserveFlags & OBSERVE_FILE_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_FILE_CHANGED); + + DeliverMessage(message); +} + + +void +ControllerObserver::VideoTrackChanged(int32 index) +{ + if (!(fObserveFlags & OBSERVE_TRACK_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_VIDEO_TRACK_CHANGED); + message.AddInt32("index", index); + + DeliverMessage(message); +} + + +void +ControllerObserver::AudioTrackChanged(int32 index) +{ + if (!(fObserveFlags & OBSERVE_TRACK_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_AUDIO_TRACK_CHANGED); + message.AddInt32("index", index); + + DeliverMessage(message); +} + + +void +ControllerObserver::VideoStatsChanged() +{ + if (!(fObserveFlags & OBSERVE_STAT_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_VIDEO_STATS_CHANGED); + + DeliverMessage(message); +} + + +void +ControllerObserver::AudioStatsChanged() +{ + if (!(fObserveFlags & OBSERVE_STAT_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_AUDIO_STATS_CHANGED); + + DeliverMessage(message); +} + + +void +ControllerObserver::PlaybackStateChanged(uint32 state) +{ + if (!(fObserveFlags & OBSERVE_PLAYBACK_STATE_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_PLAYBACK_STATE_CHANGED); + message.AddInt32("state", state); + + DeliverMessage(message); +} + + +void +ControllerObserver::PositionChanged(float position) +{ + if (!(fObserveFlags & OBSERVE_POSITION_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_POSITION_CHANGED); + message.AddFloat("position", position); + + DeliverMessage(message); +} + + +void +ControllerObserver::VolumeChanged(float volume) +{ + if (!(fObserveFlags & OBSERVE_VOLUME_CHANGES)) + return; + + BMessage message(MSG_CONTROLLER_VOLUME_CHANGED); + message.AddFloat("volume", volume); + + DeliverMessage(message); +} + + diff --git a/src/apps/mediaplayer/ControllerObserver.h b/src/apps/mediaplayer/ControllerObserver.h new file mode 100644 index 0000000000..588ada789d --- /dev/null +++ b/src/apps/mediaplayer/ControllerObserver.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef CONTROLLER_OBSERVER_H +#define CONTROLLER_OBSERVER_H + +#include "AbstractLOAdapter.h" +#include "Controller.h" + +enum { + MSG_CONTROLLER_FILE_FINISHED = 'cnff', + MSG_CONTROLLER_FILE_CHANGED = 'cnfc', + + MSG_CONTROLLER_VIDEO_TRACK_CHANGED = 'cnvt', + MSG_CONTROLLER_AUDIO_TRACK_CHANGED = 'cnat', + + MSG_CONTROLLER_VIDEO_STATS_CHANGED = 'cnvs', + MSG_CONTROLLER_AUDIO_STATS_CHANGED = 'cnas', + + MSG_CONTROLLER_PLAYBACK_STATE_CHANGED = 'cnps', + MSG_CONTROLLER_POSITION_CHANGED = 'cnpc', + MSG_CONTROLLER_VOLUME_CHANGED = 'cnvc' +}; + +enum { + OBSERVE_FILE_CHANGES = 0x0001, + OBSERVE_TRACK_CHANGES = 0x0002, + OBSERVE_STAT_CHANGES = 0x0004, + OBSERVE_PLAYBACK_STATE_CHANGES = 0x0008, + OBSERVE_POSITION_CHANGES = 0x0010, + OBSERVE_VOLUME_CHANGES = 0x0020, + + OBSERVE_ALL_CHANGES = 0xffff +}; + +class ControllerObserver : public Controller::Listener, + public AbstractLOAdapter { + public: + ControllerObserver(BHandler* target, + uint32 observeFlags = OBSERVE_ALL_CHANGES); + virtual ~ControllerObserver(); + + // Controller::Listener interface + virtual void FileFinished(); + virtual void FileChanged(); + + virtual void VideoTrackChanged(int32 index); + virtual void AudioTrackChanged(int32 index); + + virtual void VideoStatsChanged(); + virtual void AudioStatsChanged(); + + virtual void PlaybackStateChanged(uint32 state); + virtual void PositionChanged(float position); + virtual void VolumeChanged(float volume); + + private: + uint32 fObserveFlags; +}; + +#endif // CONTROLLER_OBSERVER_H diff --git a/src/apps/mediaplayer/ControllerView.cpp b/src/apps/mediaplayer/ControllerView.cpp index 42782237a2..2819213a02 100644 --- a/src/apps/mediaplayer/ControllerView.cpp +++ b/src/apps/mediaplayer/ControllerView.cpp @@ -2,6 +2,7 @@ * Controller.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,26 +18,32 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ +#include "ControllerView.h" + #include #include #include -#include "ControllerView.h" #include "Controller.h" #include "Playlist.h" -#include "Player.h" +#include "PlaylistObserver.h" -ControllerView::ControllerView(BRect frame, Controller *ctrl, Playlist *pl, Player *p) + +ControllerView::ControllerView(BRect frame, Controller* controller, + Playlist* playlist) : TransportControlGroup(frame) - , fController(ctrl) - , fPlaylist(pl) - , fPlayer(p) + , fController(controller) + , fPlaylist(playlist) + , fPlaylistObserver(new PlaylistObserver(this)) { + fPlaylist->AddListener(fPlaylistObserver); } ControllerView::~ControllerView() { + fPlaylist->RemoveListener(fPlaylistObserver); + delete fPlaylistObserver; } @@ -58,6 +65,11 @@ void ControllerView::MessageReceived(BMessage *msg) { switch (msg->what) { + case MSG_PLAYLIST_REF_ADDED: + case MSG_PLAYLIST_REF_REMOVED: + CheckSkippable(); + break; + default: TransportControlGroup::MessageReceived(msg); } @@ -69,6 +81,7 @@ ControllerView::MessageReceived(BMessage *msg) uint32 ControllerView::EnabledButtons() { + // TODO: superflous return 0xffffffff; } @@ -112,11 +125,7 @@ void ControllerView::SkipBackward() { printf("ControllerView::SkipBackward()\n"); - entry_ref ref; - if (fPlaylist->PrevRef(&ref) == B_OK) { - printf("prev ref: %s\n", ref.name); - fPlayer->OpenFile(ref); - } + fPlaylist->SetCurrentRefIndex(fPlaylist->CurrentRefIndex() - 1); } @@ -124,11 +133,7 @@ void ControllerView::SkipForward() { printf("ControllerView::SkipForward()\n"); - entry_ref ref; - if (fPlaylist->NextRef(&ref) == B_OK) { - printf("next ref: %s\n", ref.name); - fPlayer->OpenFile(ref); - } + fPlaylist->SetCurrentRefIndex(fPlaylist->CurrentRefIndex() + 1); } @@ -155,3 +160,16 @@ ControllerView::PositionChanged(float value) fController->SetPosition(value); } + +// #pragma mark - + + +void +ControllerView::CheckSkippable() +{ + bool canSkipNext, canSkipPrevious; + fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext); + SetSkippable(canSkipPrevious, canSkipNext); +} + + diff --git a/src/apps/mediaplayer/ControllerView.h b/src/apps/mediaplayer/ControllerView.h index 65a6052a1a..a432a483e7 100644 --- a/src/apps/mediaplayer/ControllerView.h +++ b/src/apps/mediaplayer/ControllerView.h @@ -2,6 +2,7 @@ * Controller.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,35 +27,39 @@ class Controller; class Playlist; -class Player; +class PlaylistObserver; class ControllerView : public TransportControlGroup { public: - ControllerView(BRect frame, Controller *ctrl, Playlist *pl, Player *p); - ~ControllerView(); + ControllerView(BRect frame, Controller* controller, + Playlist* playlist); + ~ControllerView(); private: - void AttachedToWindow(); - void MessageReceived(BMessage *msg); - void Draw(BRect updateRect); + void AttachedToWindow(); + void MessageReceived(BMessage *msg); + void Draw(BRect updateRect); // TransportControlGroup interface - virtual uint32 EnabledButtons(); - virtual void TogglePlaying(); - virtual void Stop(); - virtual void Rewind(); - virtual void Forward(); - virtual void SkipBackward(); - virtual void SkipForward(); - virtual void VolumeChanged(float value); - virtual void ToggleMute(); - virtual void PositionChanged(float value); - + virtual uint32 EnabledButtons(); + virtual void TogglePlaying(); + virtual void Stop(); + virtual void Rewind(); + virtual void Forward(); + virtual void SkipBackward(); + virtual void SkipForward(); + virtual void VolumeChanged(float value); + virtual void ToggleMute(); + virtual void PositionChanged(float value); + + // ControllerView + void CheckSkippable(); + private: - Controller * fController; - Playlist * fPlaylist; - Player * fPlayer; + Controller* fController; + Playlist* fPlaylist; + PlaylistObserver* fPlaylistObserver; }; #endif diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index 34e072044f..e472ddc185 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -4,14 +4,36 @@ SetSubDirSupportedPlatformsBeOSCompatible ; AddSubDirSupportedPlatforms libbe_test ; +# source directories +local sourceDirs = + supplier +; + +local sourceDir ; +for sourceDir in $(sourceDirs) { + SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src apps mediaplayer $(sourceDir) ] ; +} + Application MediaPlayer : + # supplier + AudioSupplier.cpp + MediaTrackAudioSupplier.cpp + MediaTrackVideoSupplier.cpp + VideoSupplier.cpp + + # . + AbstractLOAdapter.cpp Controller.cpp + ControllerObserver.cpp ControllerView.cpp DrawingTidbits.cpp InfoWin.cpp + ListViews.cpp MainApp.cpp MainWin.cpp Playlist.cpp + PlaylistObserver.cpp + PlaylistWindow.cpp SoundOutput.cpp TransportButton.cpp TransportControlGroup.cpp @@ -19,7 +41,8 @@ Application MediaPlayer : VideoNode.cpp VideoView.cpp VolumeSlider.cpp - : be media tracker $(TARGET_LIBSTDC++) + + : be media tracker translation $(TARGET_LIBSTDC++) : MediaPlayer.rdef ; diff --git a/src/apps/mediaplayer/ListViews.cpp b/src/apps/mediaplayer/ListViews.cpp new file mode 100644 index 0000000000..513e69aedd --- /dev/null +++ b/src/apps/mediaplayer/ListViews.cpp @@ -0,0 +1,891 @@ +/* + * Copyright 2006-2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ListViews.h" + +const unsigned char kCopyCursor[] = { 16, 1, 1, 1, + 0x00, 0x00, 0x70, 0x00, 0x48, 0x00, 0x48, 0x00, + 0x27, 0xc0, 0x24, 0xb8, 0x12, 0x54, 0x10, 0x02, + 0x79, 0xe2, 0x99, 0x22, 0x85, 0x7a, 0x61, 0x4a, + 0x19, 0xca, 0x04, 0x4a, 0x02, 0x78, 0x00, 0x00, + + 0x00, 0x00, 0x70, 0x00, 0x78, 0x00, 0x78, 0x00, + 0x3f, 0xc0, 0x3f, 0xf8, 0x1f, 0xfc, 0x1f, 0xfe, + 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe, + 0x1f, 0xfe, 0x07, 0xfe, 0x03, 0xf8, 0x00, 0x00 }; + +#define MAX_DRAG_HEIGHT 200.0 +#define ALPHA 170 +#define TEXT_OFFSET 5.0 + +enum { + MSG_TICK = 'tick', +}; + +using std::nothrow; + +// SimpleItem class +SimpleItem::SimpleItem( const char *name ) + : BStringItem( name ) +{ +} + +SimpleItem::~SimpleItem() +{ +} + +// SimpleItem::DrawItem +void +SimpleItem::Draw(BView *owner, BRect frame, uint32 flags) +{ + DrawBackground(owner, frame, flags); + // label + owner->SetHighColor( 0, 0, 0, 255 ); + font_height fh; + owner->GetFontHeight( &fh ); + const char* text = Text(); + BString truncatedString( text ); + owner->TruncateString( &truncatedString, B_TRUNCATE_MIDDLE, + frame.Width() - TEXT_OFFSET - 4.0 ); + float height = frame.Height(); + float textHeight = fh.ascent + fh.descent; + BPoint textPoint; + textPoint.x = frame.left + TEXT_OFFSET; + textPoint.y = frame.top + + ceilf(height / 2.0 - textHeight / 2.0 + + fh.ascent); + owner->DrawString(truncatedString.String(), textPoint); +} + +// SimpleItem::DrawBackground +void +SimpleItem::DrawBackground(BView *owner, BRect frame, uint32 flags) +{ + // stroke a blue frame around the item if it's focused + if (flags & FLAGS_FOCUSED) { + owner->SetLowColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); + owner->StrokeRect(frame, B_SOLID_LOW); + frame.InsetBy(1.0, 1.0); + } + // figure out bg-color + rgb_color color = (rgb_color){ 255, 255, 255, 255 }; + if ( flags & FLAGS_TINTED_LINE ) + color = tint_color( color, 1.06 ); + // background + if ( IsSelected() ) + color = tint_color( color, B_DARKEN_2_TINT ); + owner->SetLowColor( color ); + owner->FillRect( frame, B_SOLID_LOW ); +} + +// DragSortableListView class +DragSortableListView::DragSortableListView(BRect frame, const char* name, + list_view_type type, uint32 resizingMode, + uint32 flags) + : BListView(frame, name, type, resizingMode, flags), + fDropRect(0.0, 0.0, -1.0, -1.0), + fScrollPulse(NULL), + fDropIndex(-1), + fLastClickedItem(NULL), + fScrollView(NULL), + fDragCommand(B_SIMPLE_DATA), + fFocusedIndex(-1) +{ + SetViewColor(B_TRANSPARENT_32_BIT); +} + +DragSortableListView::~DragSortableListView() +{ + delete fScrollPulse; +} + +// AttachedToWindow +void +DragSortableListView::AttachedToWindow() +{ + BListView::AttachedToWindow(); + + // work arround a bug in BListView + BRect bounds = Bounds(); + BListView::FrameResized(bounds.Width(), bounds.Height()); +} + +// DetachedFromWindow +void +DragSortableListView::DetachedFromWindow() +{ +} + +// FrameResized +void +DragSortableListView::FrameResized(float width, float height) +{ + BListView::FrameResized(width, height); + Invalidate(); +} + +/* +// MakeFocus +void +DragSortableListView::MakeFocus(bool focused) +{ + if (focused != IsFocus()) { + Invalidate(); + BListView::MakeFocus(focused); + } +} +*/ +// Draw +void +DragSortableListView::Draw( BRect updateRect ) +{ + int32 firstIndex = IndexOf(updateRect.LeftTop()); + int32 lastIndex = IndexOf(updateRect.RightBottom()); + if (firstIndex >= 0) { + if (lastIndex < firstIndex) + lastIndex = CountItems() - 1; + // update rect contains items + BRect r = updateRect; + for (int32 i = firstIndex; i <= lastIndex; i++) { + r = ItemFrame(i); + DrawListItem(this, i, r); + } + updateRect.top = r.bottom + 1.0; + if (updateRect.IsValid()) { + SetLowColor(255, 255, 255, 255); + FillRect(updateRect, B_SOLID_LOW); + } + } else { + SetLowColor(255, 255, 255, 255); + FillRect(updateRect, B_SOLID_LOW); + } + // drop anticipation indication + if (fDropRect.IsValid()) { + SetHighColor(255, 0, 0, 255); + StrokeRect(fDropRect); + } +/* // focus indication + if (IsFocus()) { + SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); + StrokeRect(Bounds()); + }*/ +} + +// ScrollTo +void +DragSortableListView::ScrollTo(BPoint where) +{ + uint32 buttons; + BPoint point; + GetMouse(&point, &buttons, false); + uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; + MouseMoved(point, transit, &fDragMessageCopy); + BListView::ScrollTo(where); +} + +// TargetedByScrollView +void +DragSortableListView::TargetedByScrollView(BScrollView* scrollView) +{ + fScrollView = scrollView; + BListView::TargetedByScrollView(scrollView); +} + +// InitiateDrag +bool +DragSortableListView::InitiateDrag( BPoint point, int32 index, bool ) +{ + // supress drag&drop while an item is focused + if (fFocusedIndex >= 0) + return false; + + bool success = false; + BListItem* item = ItemAt( CurrentSelection( 0 ) ); + if ( !item ) { + // workarround a timing problem + Select( index ); + item = ItemAt( index ); + } + if ( item ) { + // create drag message + BMessage msg( fDragCommand ); + MakeDragMessage( &msg ); + // figure out drag rect + float width = Bounds().Width(); + BRect dragRect(0.0, 0.0, width, -1.0); + // figure out, how many items fit into our bitmap + int32 numItems; + bool fade = false; + for (numItems = 0; BListItem* item = ItemAt( CurrentSelection( numItems ) ); numItems++) { + dragRect.bottom += ceilf( item->Height() ) + 1.0; + if ( dragRect.Height() > MAX_DRAG_HEIGHT ) { + fade = true; + dragRect.bottom = MAX_DRAG_HEIGHT; + numItems++; + break; + } + } + BBitmap* dragBitmap = new BBitmap( dragRect, B_RGB32, true ); + if ( dragBitmap && dragBitmap->IsValid() ) { + if ( BView *v = new BView( dragBitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW ) ) { + dragBitmap->AddChild( v ); + dragBitmap->Lock(); + BRect itemBounds( dragRect) ; + itemBounds.bottom = 0.0; + // let all selected items, that fit into our drag_bitmap, draw + for ( int32 i = 0; i < numItems; i++ ) { + int32 index = CurrentSelection( i ); + BListItem* item = ItemAt( index ); + itemBounds.bottom = itemBounds.top + ceilf( item->Height() ); + if ( itemBounds.bottom > dragRect.bottom ) + itemBounds.bottom = dragRect.bottom; + DrawListItem( v, index, itemBounds ); + itemBounds.top = itemBounds.bottom + 1.0; + } + // make a black frame arround the edge + v->SetHighColor( 0, 0, 0, 255 ); + v->StrokeRect( v->Bounds() ); + v->Sync(); + + uint8 *bits = (uint8 *)dragBitmap->Bits(); + int32 height = (int32)dragBitmap->Bounds().Height() + 1; + int32 width = (int32)dragBitmap->Bounds().Width() + 1; + int32 bpr = dragBitmap->BytesPerRow(); + + if (fade) { + for ( int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr ) { + uint8 *line = bits + 3; + for (uint8 *end = line + 4 * width; line < end; line += 4) + *line = ALPHA; + } + for ( int32 y = height - ALPHA / 2; y < height; y++, bits += bpr ) { + uint8 *line = bits + 3; + for (uint8 *end = line + 4 * width; line < end; line += 4) + *line = (height - y) << 1; + } + } else { + for ( int32 y = 0; y < height; y++, bits += bpr ) { + uint8 *line = bits + 3; + for (uint8 *end = line + 4 * width; line < end; line += 4) + *line = ALPHA; + } + } + dragBitmap->Unlock(); + } + } else { + delete dragBitmap; + dragBitmap = NULL; + } + if (dragBitmap) + DragMessage( &msg, dragBitmap, B_OP_ALPHA, BPoint( 0.0, 0.0 ) ); + else + DragMessage( &msg, dragRect.OffsetToCopy( point ), this ); + + _SetDragMessage(&msg); + success = true; + } + return success; +} + +// WindowActivated +void +DragSortableListView::WindowActivated( bool active ) +{ + // workarround for buggy focus indication of BScrollView + if ( BView* view = Parent() ) + view->Invalidate(); +} + +// MessageReceived +void +DragSortableListView::MessageReceived(BMessage* message) +{ + if (AcceptDragMessage(message)) { + DragSortableListView *list = NULL; + if ( message->FindPointer( "list", (void **)&list ) == B_OK + && list == this ) { + int32 count = CountItems(); + if ( fDropIndex < 0 || fDropIndex > count ) + fDropIndex = count; + BList items; + int32 index; + for ( int32 i = 0; message->FindInt32( "index", i, &index ) == B_OK; i++ ) + if ( BListItem* item = ItemAt(index) ) + items.AddItem( (void*)item ); + if ( items.CountItems() > 0 ) { + if ( modifiers() & B_SHIFT_KEY ) + CopyItems( items, fDropIndex ); + else + MoveItems( items, fDropIndex ); + } + fDropIndex = -1; + } + } else { + switch ( message->what ) { + case MSG_TICK: { + float scrollV = 0.0; + BRect rect(Bounds()); + BPoint point; + uint32 buttons; + GetMouse(&point, &buttons, false); + if (rect.Contains(point)) { + // calculate the vertical scrolling offset + float hotDist = rect.Height() * SCROLL_AREA; + if (point.y > rect.bottom - hotDist) + scrollV = hotDist - (rect.bottom - point.y); + else if (point.y < rect.top + hotDist) + scrollV = (point.y - rect.top) - hotDist; + } + // scroll + if (scrollV != 0.0 && fScrollView) { + if (BScrollBar* scrollBar = fScrollView->ScrollBar(B_VERTICAL)) { + float value = scrollBar->Value(); + scrollBar->SetValue(scrollBar->Value() + scrollV); + if (scrollBar->Value() != value) { + // update mouse position + uint32 buttons; + BPoint point; + GetMouse(&point, &buttons, false); + uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; + MouseMoved(point, transit, &fDragMessageCopy); + } + } + } + break; + } +// case B_MODIFIERS_CHANGED: +// ModifiersChanged(); +// break; + case B_MOUSE_WHEEL_CHANGED: { + BListView::MessageReceived( message ); + BPoint point; + uint32 buttons; + GetMouse(&point, &buttons, false); + uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; + MouseMoved(point, transit, &fDragMessageCopy); + break; + } + default: + BListView::MessageReceived( message ); + break; + } + } +} + +// KeyDown +void +DragSortableListView::KeyDown( const char* bytes, int32 numBytes ) +{ + if ( numBytes < 1 ) + return; + + if ( ( bytes[0] == B_BACKSPACE ) || ( bytes[0] == B_DELETE ) ) + RemoveSelected(); + + BListView::KeyDown( bytes, numBytes ); +} + +// MouseDown +void +DragSortableListView::MouseDown( BPoint where ) +{ + int32 clicks = 1; + uint32 buttons = 0; + Window()->CurrentMessage()->FindInt32("clicks", &clicks); + Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); + int32 clickedIndex = -1; + for (int32 i = 0; BListItem* item = ItemAt(i); i++) { + if (ItemFrame(i).Contains(where)) { + if (clicks == 2) { + // only do something if user clicked the same item twice + if (fLastClickedItem == item) + DoubleClicked(i); + } else { + // remember last clicked item + fLastClickedItem = item; + } + clickedIndex = i; + break; + } + } + if (clickedIndex == -1) + fLastClickedItem = NULL; + + BListItem* item = ItemAt(clickedIndex); + if (ListType() == B_MULTIPLE_SELECTION_LIST + && item && (buttons & B_SECONDARY_MOUSE_BUTTON)) { + if (item->IsSelected()) + Deselect(clickedIndex); + else + Select(clickedIndex, true); + } else { + BListView::MouseDown(where); + } +} + +// MouseMoved +void +DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg) +{ + if (msg && AcceptDragMessage(msg)) { + switch (transit) { + case B_ENTERED_VIEW: + case B_INSIDE_VIEW: { + // remember drag message + // this is needed to react on modifier changes + _SetDragMessage(msg); + // set drop target through virtual function + SetDropTargetRect(msg, where); + // go into autoscrolling mode + BRect r = Bounds(); + r.InsetBy(0.0, r.Height() * SCROLL_AREA); + SetAutoScrolling(!r.Contains(where)); + break; + } + case B_EXITED_VIEW: + // forget drag message + _SetDragMessage(NULL); + SetAutoScrolling(false); + // fall through + case B_OUTSIDE_VIEW: + _RemoveDropAnticipationRect(); + break; + } + } else { + _RemoveDropAnticipationRect(); + BListView::MouseMoved(where, transit, msg); + _SetDragMessage(NULL); + SetAutoScrolling(false); + + BCursor cursor(B_HAND_CURSOR); + SetViewCursor(&cursor, true); + } + fLastMousePos = where; +} + +// MouseUp +void +DragSortableListView::MouseUp( BPoint where ) +{ + // remove drop mark + _SetDropAnticipationRect( BRect( 0.0, 0.0, -1.0, -1.0 ) ); + SetAutoScrolling(false); + // be sure to forget drag message + _SetDragMessage(NULL); + BListView::MouseUp( where ); + + BCursor cursor(B_HAND_CURSOR); + SetViewCursor(&cursor, true); +} + +// DrawItem +void +DragSortableListView::DrawItem( BListItem *item, BRect itemFrame, bool complete ) +{ + DrawListItem( this, IndexOf( item ), itemFrame ); +/* if (IsFocus()) { + SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); + StrokeRect(Bounds()); + }*/ +} + +// MouseWheelChanged +bool +DragSortableListView::MouseWheelChanged(float x, float y) +{ + BPoint where; + uint32 buttons; + GetMouse(&where, &buttons, false); + if (Bounds().Contains(where)) + return true; + else + return false; +} + +// SetDragCommand +void +DragSortableListView::SetDragCommand(uint32 command) +{ + fDragCommand = command; +} + +// ModifiersChaned +void +DragSortableListView::ModifiersChanged() +{ + SetDropTargetRect(&fDragMessageCopy, fLastMousePos); +} + +// SetItemFocused +void +DragSortableListView::SetItemFocused(int32 index) +{ + InvalidateItem(fFocusedIndex); + InvalidateItem(index); + fFocusedIndex = index; +} + +// AcceptDragMessage +bool +DragSortableListView::AcceptDragMessage(const BMessage* message) const +{ + return message->what == fDragCommand; +} + +// SetDropTargetRect +void +DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where) + +{ + if (AcceptDragMessage(message)) { + bool copy = modifiers() & B_SHIFT_KEY; + bool replaceAll = !message->HasPointer("list") && !copy; + BRect r = Bounds(); + if (replaceAll) { + r.bottom--; // compensate for scrollbar offset + _SetDropAnticipationRect(r); + fDropIndex = -1; + } else { + // offset where by half of item height + r = ItemFrame(0); + where.y += r.Height() / 2.0; + + int32 index = IndexOf(where); + if (index < 0) + index = CountItems(); + _SetDropIndex(index); + + const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR; + BCursor cursor(cursorData); + SetViewCursor(&cursor, true); + } + } +} + +// SetAutoScrolling +void +DragSortableListView::SetAutoScrolling(bool enable) +{ + if (fScrollPulse && enable) + return; + if (enable) { + BMessenger messenger(this, Window()); + BMessage message(MSG_TICK); + fScrollPulse = new BMessageRunner(messenger, &message, 40000LL); + } else { + delete fScrollPulse; + fScrollPulse = NULL; + } +} + +// DoesAutoScrolling +bool +DragSortableListView::DoesAutoScrolling() const +{ + return fScrollPulse; +} + +// ScrollTo +void +DragSortableListView::ScrollTo(int32 index) +{ + if (index < 0) + index = 0; + if (index >= CountItems()) + index = CountItems() - 1; + + if (BListItem* item = ItemAt(index)) { + BRect itemFrame = ItemFrame(index); + BRect bounds = Bounds(); + if (itemFrame.top < bounds.top) { + ScrollTo(itemFrame.LeftTop()); + } else if (itemFrame.bottom > bounds.bottom) { + ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height())); + } + } +} + +// MoveItems +void +DragSortableListView::MoveItems( BList& items, int32 index ) +{ + DeselectAll(); + // we remove the items while we look at them, the insertion index is decreased + // when the items index is lower, so that we insert at the right spot after + // removal + BList removedItems; + int32 count = items.CountItems(); + for ( int32 i = 0; i < count; i++ ) + { + BListItem* item = (BListItem*)items.ItemAt( i ); + int32 removeIndex = IndexOf( item ); + if ( RemoveItem( item ) && removedItems.AddItem( (void*)item ) ) + { + if ( removeIndex < index ) + index--; + } + // else ??? -> blow up + } + for ( int32 i = 0; BListItem* item = (BListItem*)removedItems.ItemAt( i ); i++ ) + { + if ( AddItem( item, index ) ) + { + // after we're done, the newly inserted items will be selected + Select( index, true ); + // next items will be inserted after this one + index++; + } + else + delete item; + } +} + +// CopyItems +void +DragSortableListView::CopyItems( BList& items, int32 index ) +{ + DeselectAll(); + // by inserting the items after we copied all items first, we avoid + // cloning an item we already inserted and messing everything up + // in other words, don't touch the list before we know which items + // need to be cloned + BList clonedItems; + int32 count = items.CountItems(); + for ( int32 i = 0; i < count; i++ ) + { + BListItem* item = CloneItem( IndexOf( (BListItem*)items.ItemAt( i ) ) ); + if ( item && !clonedItems.AddItem( (void*)item ) ) + delete item; + } + for ( int32 i = 0; BListItem* item = (BListItem*)clonedItems.ItemAt( i ); i++ ) + { + if ( AddItem( item, index ) ) + { + // after we're done, the newly inserted items will be selected + Select( index, true ); + // next items will be inserted after this one + index++; + } + else + delete item; + } +} + +// RemoveItemList +void +DragSortableListView::RemoveItemList( BList& items ) +{ + int32 count = items.CountItems(); + for ( int32 i = 0; i < count; i++ ) + { + BListItem* item = (BListItem*)items.ItemAt( i ); + if ( RemoveItem( item ) ) + delete item; + } +} + +// RemoveSelected +void +DragSortableListView::RemoveSelected() +{ +// if (fFocusedIndex >= 0) +// return; + + BList items; + for ( int32 i = 0; BListItem* item = ItemAt( CurrentSelection( i ) ); i++ ) + items.AddItem( (void*)item ); + RemoveItemList( items ); +} + +// CountSelectedItems +int32 +DragSortableListView::CountSelectedItems() const +{ + int32 count = 0; + while ( CurrentSelection( count ) >= 0 ) + count++; + return count; +} + +// SelectAll +void +DragSortableListView::SelectAll() +{ + Select(0, CountItems() - 1); +} + +// DeleteItem +bool +DragSortableListView::DeleteItem(int32 index) +{ + BListItem* item = ItemAt(index); + if (item && RemoveItem(item)) { + delete item; + return true; + } + return false; +} + +// _SetDropAnticipationRect +void +DragSortableListView::_SetDropAnticipationRect(BRect r) +{ + if (fDropRect != r) { + if (fDropRect.IsValid()) + Invalidate(fDropRect); + fDropRect = r; + if (fDropRect.IsValid()) + Invalidate(fDropRect); + } +} + +// _SetDropIndex +void +DragSortableListView::_SetDropIndex(int32 index) +{ + if (fDropIndex != index) { + fDropIndex = index; + if (fDropIndex >= 0) { + int32 count = CountItems(); + if (fDropIndex == count) { + BRect r; + if (BListItem* item = ItemAt(count - 1)) { + r = ItemFrame(count - 1); + r.top = r.bottom; + r.bottom = r.top + 1.0; + } else { + r = Bounds(); + r.bottom--; // compensate for scrollbars moved slightly out of window + } + _SetDropAnticipationRect(r); + } else { + BRect r = ItemFrame(fDropIndex); + r.top--; + r.bottom = r.top + 1.0; + _SetDropAnticipationRect(r); + } + } + } +} + +// _RemoveDropAnticipationRect +void +DragSortableListView::_RemoveDropAnticipationRect() +{ + _SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0)); +// _SetDropIndex(-1); +} + +// _SetDragMessage +void +DragSortableListView::_SetDragMessage(const BMessage* message) +{ + if (message) + fDragMessageCopy = *message; + else + fDragMessageCopy.what = 0; +} + + + +// SimpleListView class +SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage) + : DragSortableListView(frame, "playlist listview", + B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS), + fSelectionChangeMessage(selectionChangeMessage) +{ +} + +// SimpleListView class +SimpleListView::SimpleListView(BRect frame, const char* name, + BMessage* selectionChangeMessage, + list_view_type type, + uint32 resizingMode, uint32 flags) + : DragSortableListView(frame, name, type, resizingMode, flags), + fSelectionChangeMessage(selectionChangeMessage) +{ +} + +// destructor +SimpleListView::~SimpleListView() +{ + delete fSelectionChangeMessage; +} + +// MessageReceived +void +SimpleListView::MessageReceived( BMessage* message) +{ + switch (message->what) { + default: + DragSortableListView::MessageReceived(message); + break; + } +} + +// SelectionChanged +void +SimpleListView::SelectionChanged() +{ + BLooper* looper = Looper(); + if (fSelectionChangeMessage && looper) { + BMessage message(*fSelectionChangeMessage); + looper->PostMessage(&message); + } +} + +// CloneItem +BListItem* +SimpleListView::CloneItem(int32 atIndex) const +{ + BListItem* clone = NULL; + if (SimpleItem* item = dynamic_cast(ItemAt(atIndex))) + clone = new SimpleItem(item->Text()); + return clone; +} + +// DrawListItem +void +SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const +{ + if (SimpleItem* item = dynamic_cast(ItemAt(index))) { + uint32 flags = FLAGS_NONE; + if (index == fFocusedIndex) + flags |= FLAGS_FOCUSED; + if (index % 2) + flags |= FLAGS_TINTED_LINE; + item->Draw(owner, frame, flags); + } +} + +// MakeDragMessage +void +SimpleListView::MakeDragMessage(BMessage* message) const +{ + if (message) { + message->AddPointer( "list", (void*)dynamic_cast(this)); + int32 index; + for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++) + message->AddInt32( "index", index ); + } +} + diff --git a/src/apps/mediaplayer/ListViews.h b/src/apps/mediaplayer/ListViews.h new file mode 100644 index 0000000000..b78bc84b18 --- /dev/null +++ b/src/apps/mediaplayer/ListViews.h @@ -0,0 +1,152 @@ +/* + * Copyright 2006-2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef LIST_VIEWS_H +#define LIST_VIEWS_H + +#include +#include +#include + +enum { + FLAGS_NONE = 0x00, + FLAGS_TINTED_LINE = 0x01, + FLAGS_FOCUSED = 0x02, +}; + +// portion of the listviews height that triggers autoscrolling +// when the mouse is over it with a dragmessage +#define SCROLL_AREA 0.1 + +class BMessageRunner; +class BMessageFilter; +class InterfaceWindow; +class BScrollView; + +// SimpleItem +class SimpleItem : public BStringItem { + public: + SimpleItem(const char* name); + virtual ~SimpleItem(); + + virtual void Draw(BView* owner, BRect frame, + uint32 flags); + virtual void DrawBackground(BView* owner, BRect frame, + uint32 flags); +}; + +// DragSortableListView +class DragSortableListView : public BListView { + public: + DragSortableListView(BRect frame, const char* name, + list_view_type type = B_SINGLE_SELECTION_LIST, + uint32 resizingMode = B_FOLLOW_LEFT + | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW | B_NAVIGABLE + | B_FRAME_EVENTS); + virtual ~DragSortableListView(); + + // BListView interface + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void FrameResized(float width, float height); + virtual void Draw(BRect updateRect); + virtual void ScrollTo(BPoint where); + virtual void TargetedByScrollView(BScrollView* scrollView); + virtual bool InitiateDrag(BPoint point, int32 index, + bool wasSelected); + virtual void MessageReceived(BMessage* message); + virtual void KeyDown(const char* bytes, int32 numBytes); + virtual void MouseDown(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + virtual void MouseUp(BPoint where); + virtual void WindowActivated(bool active); + virtual void DrawItem(BListItem *item, BRect itemFrame, + bool complete = false); + + // DragSortableListView + virtual void SetDragCommand(uint32 command); + virtual void ModifiersChanged(); // called by window + virtual void DoubleClicked(int32 index) {} + + virtual void SetItemFocused(int32 index); + + virtual bool AcceptDragMessage(const BMessage* message) const; + virtual void SetDropTargetRect(const BMessage* message, + BPoint where); + + // autoscrolling + void SetAutoScrolling(bool enable); + bool DoesAutoScrolling() const; + BScrollView* ScrollView() const + { return fScrollView; } + void ScrollTo(int32 index); + + bool MouseWheelChanged(float x, float y); + + virtual void MoveItems(BList& items, int32 toIndex); + virtual void CopyItems(BList& items, int32 toIndex); + virtual void RemoveItemList(BList& indices); + void RemoveSelected(); // uses RemoveItemList() + int32 CountSelectedItems() const; + void SelectAll(); + virtual bool DeleteItem(int32 index); + + virtual BListItem* CloneItem(int32 atIndex) const = 0; + virtual void DrawListItem(BView* owner, int32 index, + BRect itemFrame) const = 0; + virtual void MakeDragMessage(BMessage* message) const = 0; + + private: + void _RemoveDropAnticipationRect(); + void _SetDragMessage(const BMessage* message); + + BRect fDropRect; + BMessage fDragMessageCopy; + BMessageRunner* fScrollPulse; + BPoint fLastMousePos; + + protected: + void _SetDropAnticipationRect(BRect r); + void _SetDropIndex(int32 index); + + int32 fDropIndex; + BListItem* fLastClickedItem; + BScrollView* fScrollView; + uint32 fDragCommand; + int32 fFocusedIndex; +}; + +// SimpleListView +class SimpleListView : public DragSortableListView { + public: + SimpleListView(BRect frame, + BMessage* selectionChangeMessage = NULL); + SimpleListView(BRect frame, const char* name, + BMessage* selectionChangeMessage = NULL, + list_view_type type = B_MULTIPLE_SELECTION_LIST, + uint32 resizingMode = B_FOLLOW_ALL_SIDES, + uint32 flags = B_WILL_DRAW | B_NAVIGABLE + | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE); + ~SimpleListView(); + + // DragSortableListView interface + virtual void MessageReceived(BMessage* message); + virtual void SelectionChanged(); + + virtual BListItem* CloneItem(int32 atIndex) const; + virtual void DrawListItem(BView* owner, int32 index, + BRect itemFrame) const; + virtual void MakeDragMessage(BMessage* message) const; + + private: + + BMessage* fSelectionChangeMessage; +}; + +#endif // LIST_VIEWS_H diff --git a/src/apps/mediaplayer/MainWin.cpp b/src/apps/mediaplayer/MainWin.cpp index 54957a02c7..5b17ac1970 100644 --- a/src/apps/mediaplayer/MainWin.cpp +++ b/src/apps/mediaplayer/MainWin.cpp @@ -2,6 +2,7 @@ * MainWin.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,7 +19,6 @@ * */ #include "MainWin.h" -#include "MainApp.h" #include #include @@ -35,6 +35,11 @@ #include #include +#include "ControllerObserver.h" +#include "PlaylistObserver.h" +#include "PlaylistWindow.h" +#include "MainApp.h" + #define NAME "MediaPlayer" #define MIN_WIDTH 250 @@ -48,6 +53,7 @@ enum M_FILE_OPEN = 0x1000, M_FILE_NEWPLAYER, M_FILE_INFO, + M_FILE_PLAYLIST, M_FILE_ABOUT, M_FILE_CLOSE, M_FILE_QUIT, @@ -79,6 +85,8 @@ enum M_SELECT_AUDIO_TRACK_END = 0x00000fff, M_SELECT_VIDEO_TRACK = 0x00010000, M_SELECT_VIDEO_TRACK_END = 0x000fffff, + + M_SET_PLAYLIST_POSITION }; //#define printf(a...) @@ -88,11 +96,17 @@ MainWin::MainWin() : BWindow(BRect(100,100,350,300), NAME, B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS /* | B_WILL_ACCEPT_FIRST_CLICK */) , fFilePanel(NULL) , fInfoWin(NULL) + , fPlaylistWindow(NULL) , fInfoWinShowing(false) , fHasFile(false) , fHasVideo(false) + , fHasAudio(false) , fPlaylist(new Playlist) + , fPlaylistObserver(new PlaylistObserver(this)) , fController(new Controller) + , fControllerObserver(new ControllerObserver(this, + OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES + | OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES)) , fIsFullscreen(false) , fKeepAspectRatio(true) , fAlwaysOnTop(false) @@ -121,6 +135,7 @@ MainWin::MainWin() CreateMenu(); fBackground->AddChild(fMenuBar); fMenuBar->ResizeToPreferred(); + fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1; fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1; fMenuBar->SetResizingMode(B_FOLLOW_NONE); @@ -131,7 +146,7 @@ MainWin::MainWin() // controls rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right, fBackground->Bounds().bottom); - fControls = new ControllerView(rect, fController, fPlaylist, this); + fControls = new ControllerView(rect, fController, fPlaylist); fBackground->AddChild(fControls); fControls->ResizeToPreferred(); fControlsHeight = (int)fControls->Frame().Height() + 1; @@ -140,17 +155,18 @@ MainWin::MainWin() // fControls->MoveTo(0, fBackground->Bounds().bottom - fControlsHeight + 1); // fVideoView->ResizeTo(fBackground->Bounds().Width(), fBackground->Bounds().Height() - fMenuBarHeight - fControlsHeight); - + + fPlaylist->AddListener(fPlaylistObserver); fController->SetVideoView(fVideoView); - fController->SetControllerView(fControls); + fController->AddListener(fControllerObserver); fVideoView->IsOverlaySupported(); - printf("fMenuBarHeight %d\n", fMenuBarHeight); - printf("fControlsHeight %d\n", fControlsHeight); - printf("fControlsWidth %d\n", fControlsWidth); +// printf("fMenuBarHeight %d\n", fMenuBarHeight); +// printf("fControlsHeight %d\n", fControlsHeight); +// printf("fControlsWidth %d\n", fControlsWidth); SetupWindow(); - + Show(); } @@ -158,13 +174,27 @@ MainWin::MainWin() MainWin::~MainWin() { printf("MainWin::~MainWin\n"); - delete fPlaylist; - delete fController; - delete fFilePanel; + + fPlaylist->RemoveListener(fPlaylistObserver); + fController->RemoveListener(fControllerObserver); + + // give the views a chance to detach from any notifiers + // before we delete them + fBackground->RemoveSelf(); + delete fBackground; + if (fInfoWin) { fInfoWin->Lock(); fInfoWin->Quit(); } + if (fPlaylistWindow) { + fPlaylistWindow->Lock(); + fPlaylistWindow->Quit(); + } + + delete fPlaylist; + delete fController; + delete fFilePanel; } @@ -175,16 +205,26 @@ MainWin::OpenFile(const entry_ref &ref) status_t err = fController->SetTo(ref); if (err != B_OK) { - char s[300]; - sprintf(s, "Can't open file\n\n%s\n\nError 0x%08lx\n(%s)\n", - ref.name, err, strerror(err)); - (new BAlert("error", s, "OK"))->Go(); + if (fPlaylist->CountItems() == 1) { + // display error if this is the only file we're supposed to play + char s[300]; + sprintf(s, "Can't open file\n\n%s\n\nError 0x%08lx\n(%s)\n", + ref.name, err, strerror(err)); + (new BAlert("error", s, "OK"))->Go(); + } else { + // just go to the next file and don't bother user + // TODO: this makes it impossible to skip backwards + // over a non recognized file! + fPlaylist->SetCurrentRefIndex(fPlaylist->CurrentRefIndex() + 1); + } fHasFile = false; fHasVideo = false; + fHasAudio = false; SetTitle(NAME); } else { fHasFile = true; fHasVideo = fController->VideoTrackCount() != 0; + fHasAudio = fController->AudioTrackCount() != 0; SetTitle(ref.name); } SetupWindow(); @@ -198,11 +238,15 @@ MainWin::SetupWindow() // Populate the track menus SetupTrackMenus(); // Enable both if a file was loaded - fAudioMenu->SetEnabled(fHasFile); - fVideoMenu->SetEnabled(fHasFile); + fAudioTrackMenu->SetEnabled(fHasFile); + fVideoTrackMenu->SetEnabled(fHasFile); // Select first track (might be "none") in both - fAudioMenu->ItemAt(0)->SetMarked(true); - fVideoMenu->ItemAt(0)->SetMarked(true); + fAudioTrackMenu->ItemAt(0)->SetMarked(true); + fVideoTrackMenu->ItemAt(0)->SetMarked(true); + + fVideoMenu->SetEnabled(fHasVideo); + fAudioMenu->SetEnabled(fHasAudio); + fDebugMenu->SetEnabled(fHasVideo); if (fHasVideo) { fController->GetSize(&fSourceWidth, &fSourceHeight); @@ -214,7 +258,9 @@ MainWin::SetupWindow() fWidthScale = 1.0; fHeightScale = 1.0; } - + + UpdateControlsEnabledStatus(); + ResizeWindow(100); fVideoView->MakeFocus(); @@ -238,7 +284,11 @@ MainWin::ResizeWindow(int percent) // Calculate and set the initial window size int width = max_c(fControlsWidth, video_width); - int height = (fNoMenu ? 0 : fMenuBarHeight) + (fNoControls ? 0 : fControlsHeight) + video_height; + int height = (fNoControls ? 0 : fControlsHeight) + video_height; + if (!fNoMenu) { + width = max_c(width, fMenuBarWidth); + height += fMenuBarHeight; + } SetWindowSizeLimits(); ResizeTo(width - 1, height - 1); } @@ -247,10 +297,12 @@ MainWin::ResizeWindow(int percent) void MainWin::SetWindowSizeLimits() { - int min_width = fNoControls ? MIN_WIDTH : fControlsWidth; - int min_height = (fNoMenu ? 0 : fMenuBarHeight) + (fNoControls ? 0 : fControlsHeight); + int minWidth = fNoControls ? MIN_WIDTH : fControlsWidth; + if (!fNoMenu) + minWidth = max_c(minWidth, fMenuBarWidth); + int minHeight = (fNoMenu ? 0 : fMenuBarHeight) + (fNoControls ? 0 : fControlsHeight); - SetSizeLimits(min_width - 1, 32000, min_height - 1, fHasVideo ? 32000 : min_height - 1); + SetSizeLimits(minWidth - 1, 32000, minHeight - 1, fHasVideo ? 32000 : minHeight - 1); } @@ -258,48 +310,55 @@ void MainWin::CreateMenu() { fFileMenu = new BMenu(NAME); - fViewMenu = new BMenu("View"); + fPlaylistMenu = new BMenu("Playlist"B_UTF8_ELLIPSIS); + fAudioMenu = new BMenu("Audio"); + fVideoMenu = new BMenu("Video"); fSettingsMenu = new BMenu("Settings"); - fAudioMenu = new BMenu("Audio Track"); - fVideoMenu = new BMenu("Video Track"); + fAudioTrackMenu = new BMenu("Track"); + fVideoTrackMenu = new BMenu("Track"); fDebugMenu = new BMenu("Debug"); fMenuBar->AddItem(fFileMenu); - fMenuBar->AddItem(fViewMenu); + fMenuBar->AddItem(fAudioMenu); + fMenuBar->AddItem(fVideoMenu); fMenuBar->AddItem(fSettingsMenu); - fMenuBar->AddItem(fDebugMenu); - - fFileMenu->AddItem(new BMenuItem("New Player", new BMessage(M_FILE_NEWPLAYER), 'N', B_COMMAND_KEY)); +// fMenuBar->AddItem(fDebugMenu); + + fFileMenu->AddItem(new BMenuItem("New Player"B_UTF8_ELLIPSIS, new BMessage(M_FILE_NEWPLAYER), 'N', B_COMMAND_KEY)); fFileMenu->AddSeparatorItem(); fFileMenu->AddItem(new BMenuItem("Open File"B_UTF8_ELLIPSIS, new BMessage(M_FILE_OPEN), 'O', B_COMMAND_KEY)); fFileMenu->AddItem(new BMenuItem("File Info"B_UTF8_ELLIPSIS, new BMessage(M_FILE_INFO), 'I', B_COMMAND_KEY)); + fFileMenu->AddItem(fPlaylistMenu); + fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY); + fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST)); + fFileMenu->AddSeparatorItem(); fFileMenu->AddItem(new BMenuItem("About" NAME B_UTF8_ELLIPSIS, new BMessage(M_FILE_ABOUT))); fFileMenu->AddSeparatorItem(); fFileMenu->AddItem(new BMenuItem("Close", new BMessage(M_FILE_CLOSE), 'W', B_COMMAND_KEY)); fFileMenu->AddItem(new BMenuItem("Quit", new BMessage(M_FILE_QUIT), 'Q', B_COMMAND_KEY)); - fViewMenu->AddItem(new BMenuItem("50% scale", new BMessage(M_VIEW_50), '0', B_COMMAND_KEY)); - fViewMenu->AddItem(new BMenuItem("100% scale", new BMessage(M_VIEW_100), '1', B_COMMAND_KEY)); - fViewMenu->AddItem(new BMenuItem("200% scale", new BMessage(M_VIEW_200), '2', B_COMMAND_KEY)); - fViewMenu->AddItem(new BMenuItem("300% scale", new BMessage(M_VIEW_300), '3', B_COMMAND_KEY)); - fViewMenu->AddItem(new BMenuItem("400% scale", new BMessage(M_VIEW_400), '4', B_COMMAND_KEY)); - fViewMenu->AddSeparatorItem(); - fViewMenu->AddItem(new BMenuItem("Full Screen", new BMessage(M_TOGGLE_FULLSCREEN), 'F', B_COMMAND_KEY)); -// fViewMenu->SetRadioMode(true); -// fViewMenu->AddSeparatorItem(); -// fViewMenu->AddItem(new BMenuItem("Always on Top", new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'T', B_COMMAND_KEY)); + fPlaylistMenu->SetRadioMode(true); + + fAudioMenu->AddItem(fAudioTrackMenu); + + fVideoMenu->AddItem(fVideoTrackMenu); + fVideoMenu->AddSeparatorItem(); + fVideoMenu->AddItem(new BMenuItem("50% scale", new BMessage(M_VIEW_50), '0', B_COMMAND_KEY)); + fVideoMenu->AddItem(new BMenuItem("100% scale", new BMessage(M_VIEW_100), '1', B_COMMAND_KEY)); + fVideoMenu->AddItem(new BMenuItem("200% scale", new BMessage(M_VIEW_200), '2', B_COMMAND_KEY)); + fVideoMenu->AddItem(new BMenuItem("300% scale", new BMessage(M_VIEW_300), '3', B_COMMAND_KEY)); + fVideoMenu->AddItem(new BMenuItem("400% scale", new BMessage(M_VIEW_400), '4', B_COMMAND_KEY)); + fVideoMenu->AddSeparatorItem(); + fVideoMenu->AddItem(new BMenuItem("Full Screen", new BMessage(M_TOGGLE_FULLSCREEN), 'F', B_COMMAND_KEY)); + fVideoMenu->AddItem(new BMenuItem("Keep Aspect Ratio", new BMessage(M_TOGGLE_KEEP_ASPECT_RATIO), 'K', B_COMMAND_KEY)); - fSettingsMenu->AddItem(fAudioMenu); - fSettingsMenu->AddItem(fVideoMenu); - fSettingsMenu->AddSeparatorItem(); fSettingsMenu->AddItem(new BMenuItem("No Menu", new BMessage(M_TOGGLE_NO_MENU), 'M', B_COMMAND_KEY)); fSettingsMenu->AddItem(new BMenuItem("No Border", new BMessage(M_TOGGLE_NO_BORDER), 'B', B_COMMAND_KEY)); fSettingsMenu->AddItem(new BMenuItem("No Controls", new BMessage(M_TOGGLE_NO_CONTROLS), 'C', B_COMMAND_KEY)); fSettingsMenu->AddItem(new BMenuItem("Always on Top", new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'T', B_COMMAND_KEY)); - fSettingsMenu->AddItem(new BMenuItem("Keep Aspect Ratio", new BMessage(M_TOGGLE_KEEP_ASPECT_RATIO), 'K', B_COMMAND_KEY)); - fSettingsMenu->AddSeparatorItem(); - fSettingsMenu->AddItem(new BMenuItem("Preferences"B_UTF8_ELLIPSIS, new BMessage(M_PREFERENCES), 'P', B_COMMAND_KEY)); +// fSettingsMenu->AddSeparatorItem(); +// fSettingsMenu->AddItem(new BMenuItem("Preferences"B_UTF8_ELLIPSIS, new BMessage(M_PREFERENCES), 'P', B_COMMAND_KEY)); fDebugMenu->AddItem(new BMenuItem("pixel aspect ratio 1.00000:1", new BMessage(M_ASPECT_100000_1))); fDebugMenu->AddItem(new BMenuItem("pixel aspect ratio 1.06666:1", new BMessage(M_ASPECT_106666_1))); @@ -309,8 +368,8 @@ MainWin::CreateMenu() fDebugMenu->AddItem(new BMenuItem("force 704 x 576, display aspect 4:3", new BMessage(M_ASPECT_704_576))); fDebugMenu->AddItem(new BMenuItem("force 544 x 576, display aspect 4:3", new BMessage(M_ASPECT_544_576))); - fAudioMenu->SetRadioMode(true); - fVideoMenu->SetRadioMode(true); + fAudioTrackMenu->SetRadioMode(true); + fVideoTrackMenu->SetRadioMode(true); /* fSettingsMenu->ItemAt(3)->SetMarked(fIsFullscreen); fSettingsMenu->ItemAt(5)->SetMarked(fNoMenu); @@ -325,8 +384,8 @@ MainWin::CreateMenu() void MainWin::SetupTrackMenus() { - fAudioMenu->RemoveItems(0, fAudioMenu->CountItems(), true); - fVideoMenu->RemoveItems(0, fVideoMenu->CountItems(), true); + fAudioTrackMenu->RemoveItems(0, fAudioTrackMenu->CountItems(), true); + fVideoTrackMenu->RemoveItems(0, fVideoTrackMenu->CountItems(), true); int c, i; char s[100]; @@ -334,18 +393,18 @@ MainWin::SetupTrackMenus() c = fController->AudioTrackCount(); for (i = 0; i < c; i++) { sprintf(s, "Track %d", i + 1); - fAudioMenu->AddItem(new BMenuItem(s, new BMessage(M_SELECT_AUDIO_TRACK + i))); + fAudioTrackMenu->AddItem(new BMenuItem(s, new BMessage(M_SELECT_AUDIO_TRACK + i))); } if (!c) - fAudioMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY))); + fAudioTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY))); c = fController->VideoTrackCount(); for (i = 0; i < c; i++) { sprintf(s, "Track %d", i + 1); - fVideoMenu->AddItem(new BMenuItem(s, new BMessage(M_SELECT_VIDEO_TRACK + i))); + fVideoTrackMenu->AddItem(new BMenuItem(s, new BMessage(M_SELECT_VIDEO_TRACK + i))); } if (!c) - fVideoMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY))); + fVideoTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY))); } @@ -358,7 +417,7 @@ MainWin::QuitRequested() void -MainWin::MouseDown(BMessage *msg) +MainWin::MouseDown(BMessage *msg, BView* originalHandler) { BPoint screen_where; uint32 buttons = msg->FindInt32("buttons"); @@ -366,8 +425,9 @@ MainWin::MouseDown(BMessage *msg) // On Zeta, only "screen_where" is relyable, "where" and "be:view_where" seem to be broken if (B_OK != msg->FindPoint("screen_where", &screen_where)) { // Workaround for BeOS R5, it has no "screen_where" - fVideoView->GetMouse(&screen_where, &buttons, false); - fVideoView->ConvertToScreen(&screen_where); + if (!originalHandler || msg->FindPoint("where", &screen_where) < B_OK) + return; + originalHandler->ConvertToScreen(&screen_where); } // msg->PrintToStream(); @@ -423,7 +483,7 @@ MainWin::MouseDown(BMessage *msg) void -MainWin::MouseMoved(BMessage *msg) +MainWin::MouseMoved(BMessage *msg, BView* originalHandler) { // msg->PrintToStream(); @@ -437,11 +497,13 @@ MainWin::MouseMoved(BMessage *msg) printf("view where: %.0f, %.0f => ", mousePos.x, mousePos.y); fVideoView->ConvertToScreen(&mousePos); */ - // On Zeta, only "screen_where" is relyable, "where" and "be:view_where" seem to be broken + // On Zeta, only "screen_where" is relyable, "where" + // and "be:view_where" seem to be broken if (B_OK != msg->FindPoint("screen_where", &mousePos)) { // Workaround for BeOS R5, it has no "screen_where" - fVideoView->GetMouse(&mousePos, &buttons, false); - fVideoView->ConvertToScreen(&mousePos); + if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK) + return; + originalHandler->ConvertToScreen(&mousePos); } // printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y); float delta_x = mousePos.x - fMouseDownMousePos.x; @@ -470,6 +532,11 @@ MainWin::ShowContextMenu(const BPoint &screen_point) BMenuItem *item; menu->AddItem(item = new BMenuItem("Full Screen", new BMessage(M_TOGGLE_FULLSCREEN), 'F', B_COMMAND_KEY)); item->SetMarked(fIsFullscreen); + item->SetEnabled(fHasVideo); + menu->AddItem(item = new BMenuItem("Keep Aspect Ratio", new BMessage(M_TOGGLE_KEEP_ASPECT_RATIO), 'K', B_COMMAND_KEY)); + item->SetMarked(fKeepAspectRatio); + item->SetEnabled(fHasVideo); + menu->AddSeparatorItem(); menu->AddItem(item = new BMenuItem("No Menu", new BMessage(M_TOGGLE_NO_MENU), 'M', B_COMMAND_KEY)); item->SetMarked(fNoMenu); @@ -477,8 +544,7 @@ MainWin::ShowContextMenu(const BPoint &screen_point) item->SetMarked(fNoBorder); menu->AddItem(item = new BMenuItem("Always on Top", new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'T', B_COMMAND_KEY)); item->SetMarked(fAlwaysOnTop); - menu->AddItem(item = new BMenuItem("Keep Aspect Ratio", new BMessage(M_TOGGLE_KEEP_ASPECT_RATIO), 'K', B_COMMAND_KEY)); - item->SetMarked(fKeepAspectRatio); + menu->AddSeparatorItem(); menu->AddItem(new BMenuItem("About" NAME B_UTF8_ELLIPSIS, new BMessage(M_FILE_ABOUT))); menu->AddSeparatorItem(); @@ -592,6 +658,15 @@ MainWin::ShowFileInfo() { if (!fInfoWin) fInfoWin = new InfoWin(this); + + if (fInfoWin->Lock()) { + if (fInfoWin->IsHidden()) + fInfoWin->Show(); + else + fInfoWin->Activate(); + fInfoWin->Unlock(); + } + BMessenger msgr(fInfoWin); BMessage m(M_UPDATE_INFO); m.AddInt32("which", INFO_ALL); @@ -599,6 +674,27 @@ MainWin::ShowFileInfo() msgr.SendMessage(B_WINDOW_ACTIVATED); } + +void +MainWin::ShowPlaylistWindow() +{ + if (!fPlaylistWindow) { + fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 400, 500), + fPlaylist); + fPlaylistWindow->Show(); + return; + } + + if (fPlaylistWindow->Lock()) { + if (fPlaylistWindow->IsHidden()) + fPlaylistWindow->Show(); + else + fPlaylistWindow->Activate(); + fPlaylistWindow->Unlock(); + } +} + + void MainWin::MaybeUpdateFileInfo(uint32 which) { @@ -776,29 +872,110 @@ MainWin::ToggleKeepAspectRatio() } +void +MainWin::UpdateControlsEnabledStatus() +{ + uint32 enabledButtons = 0; + if (fHasVideo || fHasAudio) { + enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED + | SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED; + } + if (fHasAudio) + enabledButtons |= VOLUME_ENABLED; + + bool canSkipPrevious, canSkipNext; + fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext); + if (canSkipPrevious) + enabledButtons |= SKIP_BACK_ENABLED; + if (canSkipNext) + enabledButtons |= SKIP_FORWARD_ENABLED; + + fControls->SetEnabled(enabledButtons); +} + + +void +MainWin::_UpdatePlaylistMenu() +{ + fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true); + + // TODO: lock fPlaylist + + int32 count = fPlaylist->CountItems(); + for (int32 i = 0; i < count; i++) { + entry_ref ref; + if (fPlaylist->GetRefAt(i, &ref) < B_OK) + continue; + _AddPlaylistItem(ref, i); + } + fPlaylistMenu->SetTargetForItems(this); + + _MarkPlaylistItem(fPlaylist->CurrentRefIndex()); +} + + +void +MainWin::_AddPlaylistItem(const entry_ref& ref, int32 index) +{ + BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION); + message->AddInt32("index", index); + BMenuItem* item = new BMenuItem(ref.name, message); + fPlaylistMenu->AddItem(item, index); +} + + +void +MainWin::_RemovePlaylistItem(int32 index) +{ + delete fPlaylistMenu->RemoveItem(index); +} + + +void +MainWin::_MarkPlaylistItem(int32 index) +{ + if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) { + item->SetMarked(true); + // ... and in case the menu is currently on screen: + if (fPlaylistMenu->LockLooper()) { + fPlaylistMenu->Invalidate(); + fPlaylistMenu->UnlockLooper(); + } + } +} + + void MainWin::RefsReceived(BMessage *msg) { printf("MainWin::RefsReceived\n"); - + // the playlist ist replaced by dropped files - - fPlaylist->MakeEmpty(); - + // or the dropped files are appended to the end + // of the existing playlist if is pressed + bool add = modifiers() & B_SHIFT_KEY; + + if (!add) + fPlaylist->MakeEmpty(); + + bool startPlaying = fPlaylist->CountItems() == 0; + + Playlist temporaryPlaylist; + Playlist* playlist = add ? &temporaryPlaylist : fPlaylist; + entry_ref ref; - for (int i = 0; B_OK == msg->FindRef("refs", i, &ref); i++) { - BEntry entry(&ref, true); - if (!entry.Exists() || !entry.IsFile()) - continue; - fPlaylist->AddRef(ref); + for (int i = 0; B_OK == msg->FindRef("refs", i, &ref); i++) + _AppendToPlaylist(ref, playlist); + + playlist->Sort(); + + if (add) + fPlaylist->AdoptPlaylist(temporaryPlaylist); + + if (startPlaying) { + // open first file + fPlaylist->SetCurrentRefIndex(0); } - - fPlaylist->Sort(); - - // open first file - if (fPlaylist->NextRef(&ref) == B_OK) - OpenFile(ref); - } @@ -818,7 +995,10 @@ MainWin::KeyDown(BMessage *msg) switch (raw_char) { case B_SPACE: - PostMessage(M_TOGGLE_NO_BORDER_NO_MENU_NO_CONTROLS); + if (fController->IsPaused() || fController->IsStopped()) + fController->Play(); + else + fController->Pause(); return B_OK; case B_ESCAPE: @@ -928,14 +1108,22 @@ MainWin::KeyDown(BMessage *msg) void MainWin::DispatchMessage(BMessage *msg, BHandler *handler) { - if ((msg->what == B_MOUSE_DOWN) && (handler == fBackground || handler == fVideoView)) - MouseDown(msg); - if ((msg->what == B_MOUSE_MOVED) && (handler == fBackground || handler == fVideoView)) - MouseMoved(msg); - if ((msg->what == B_MOUSE_UP) && (handler == fBackground || handler == fVideoView)) + if ((msg->what == B_MOUSE_DOWN) + && (handler == fBackground || handler == fVideoView + || handler == fControls)) + MouseDown(msg, dynamic_cast(handler)); + + if ((msg->what == B_MOUSE_MOVED) + && (handler == fBackground || handler == fVideoView + || handler == fControls)) + MouseMoved(msg, dynamic_cast(handler)); + + if ((msg->what == B_MOUSE_UP) + && (handler == fBackground || handler == fVideoView)) MouseUp(msg); - if ((msg->what == B_KEY_DOWN) && (handler == fBackground || handler == fVideoView)) { + if ((msg->what == B_KEY_DOWN) + && (handler == fBackground || handler == fVideoView)) { // special case for PrintScreen key if (msg->FindInt32("key") == B_PRINT_KEY) { @@ -969,6 +1157,75 @@ MainWin::MessageReceived(BMessage *msg) if (msg->HasRef("refs")) RefsReceived(msg); break; + + // PlaylistObserver messages + case MSG_PLAYLIST_REF_ADDED: { + entry_ref ref; + int32 index; + if (msg->FindRef("refs", &ref) == B_OK + && msg->FindInt32("index", &index) == B_OK) { + _AddPlaylistItem(ref, index); + } + break; + } + case MSG_PLAYLIST_REF_REMOVED: { + int32 index; + if (msg->FindInt32("index", &index) == B_OK) { + _RemovePlaylistItem(index); + } + break; + } + case MSG_PLAYLIST_CURRENT_REF_CHANGED: { + entry_ref ref; + int32 index = fPlaylist->CurrentRefIndex(); + if (fPlaylist->GetRefAt(index, &ref) == B_OK) { + printf("open ref: %s\n", ref.name); + OpenFile(ref); + _MarkPlaylistItem(index); + } + break; + } + + // ControllerObserver messages + case MSG_CONTROLLER_FILE_FINISHED: + fPlaylist->SetCurrentRefIndex(fPlaylist->CurrentRefIndex() + 1); + break; + case MSG_CONTROLLER_FILE_CHANGED: + // TODO: move all other GUI changes as a reaction to this notification +// _UpdatePlaylistMenu(); + break; + case MSG_CONTROLLER_VIDEO_TRACK_CHANGED: { + int32 index; + if (msg->FindInt32("index", &index) == B_OK) { + BMenuItem* item = fVideoTrackMenu->ItemAt(index); + if (item) + item->SetMarked(true); + } + break; + } + case MSG_CONTROLLER_AUDIO_TRACK_CHANGED: { + int32 index; + if (msg->FindInt32("index", &index) == B_OK) { + BMenuItem* item = fAudioTrackMenu->ItemAt(index); + if (item) + item->SetMarked(true); + } + break; + } + case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: { + uint32 state; + if (msg->FindInt32("state", (int32*)&state) == B_OK) + fControls->SetPlaybackState(state); + break; + } + case MSG_CONTROLLER_POSITION_CHANGED: { + float position; + if (msg->FindFloat("position", &position) == B_OK) + fControls->SetPosition(position); + break; + } + + // menu item messages case M_FILE_NEWPLAYER: gMainApp->NewWindow(); break; @@ -983,6 +1240,9 @@ MainWin::MessageReceived(BMessage *msg) case M_FILE_INFO: ShowFileInfo(); break; + case M_FILE_PLAYLIST: + ShowPlaylistWindow(); + break; case M_FILE_ABOUT: BAlert *alert; alert = new BAlert("about", NAME"\n\n Written by Marcus Overhagen","Thanks"); @@ -1176,8 +1436,39 @@ MainWin::MessageReceived(BMessage *msg) break; } */ + case M_SET_PLAYLIST_POSITION: { + int32 index; + if (msg->FindInt32("index", &index) == B_OK) + fPlaylist->SetCurrentRefIndex(index); + break; + } + default: // let BWindow handle the rest BWindow::MessageReceived(msg); } } + + +void +MainWin::_AppendToPlaylist(const entry_ref& ref, Playlist* playlist) +{ + // recursively append the ref (dive into folders) + BEntry entry(&ref, true); + if (entry.InitCheck() < B_OK) + return; + if (!entry.Exists()) + return; + if (entry.IsDirectory()) { + BDirectory dir(&entry); + if (dir.InitCheck() < B_OK) + return; + entry.Unset(); + entry_ref subRef; + while (dir.GetNextRef(&subRef) == B_OK) + _AppendToPlaylist(subRef, playlist); + } else if (entry.IsFile()) { + playlist->AddRef(ref); + } +} + diff --git a/src/apps/mediaplayer/MainWin.h b/src/apps/mediaplayer/MainWin.h index f6f880fea6..84015e8e96 100644 --- a/src/apps/mediaplayer/MainWin.h +++ b/src/apps/mediaplayer/MainWin.h @@ -2,6 +2,7 @@ * MainWin.h - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,11 +30,13 @@ #include "ControllerView.h" #include "InfoWin.h" #include "VideoView.h" -#include "Player.h" #include "Playlist.h" -class MainWin : public BWindow, public Player -{ +class ControllerObserver; +class PlaylistObserver; +class PlaylistWindow; + +class MainWin : public BWindow { public: MainWin(); ~MainWin(); @@ -45,8 +48,8 @@ public: void MessageReceived(BMessage *msg); bool QuitRequested(); - void MouseDown(BMessage *msg); - void MouseMoved(BMessage *msg); + void MouseDown(BMessage *msg, BView* originalHandler); + void MouseMoved(BMessage *msg, BView* originalHandler); void MouseUp(BMessage *msg); status_t KeyDown(BMessage *msg); @@ -58,9 +61,9 @@ public: void ResizeVideoView(int x, int y, int width, int height); void ShowFileInfo(); + void ShowPlaylistWindow(); void MaybeUpdateFileInfo(uint32 which=INFO_ALL); - // from Player void OpenFile(const entry_ref &ref); void VideoFormatChange(int width, int height, float width_scale, float height_scale); @@ -74,27 +77,39 @@ public: void ToggleNoBorderNoMenu(); void ShowContextMenu(const BPoint &screen_point); - + + void UpdateControlsEnabledStatus(); + void _UpdatePlaylistMenu(); + void _AddPlaylistItem(const entry_ref& ref, int32 index); + void _RemovePlaylistItem(int32 index); + void _MarkPlaylistItem(int32 index); + BMenuBar * fMenuBar; BView * fBackground; VideoView * fVideoView; BFilePanel * fFilePanel; ControllerView * fControls; InfoWin * fInfoWin; + PlaylistWindow* fPlaylistWindow; bool fInfoWinShowing; BMenu * fFileMenu; - BMenu * fViewMenu; BMenu * fAudioMenu; BMenu * fVideoMenu; + BMenu * fAudioTrackMenu; + BMenu * fVideoTrackMenu; BMenu * fSettingsMenu; + BMenu * fPlaylistMenu; BMenu * fDebugMenu; bool fHasFile; bool fHasVideo; + bool fHasAudio; Playlist * fPlaylist; + PlaylistObserver* fPlaylistObserver; Controller * fController; + ControllerObserver* fControllerObserver; volatile bool fIsFullscreen; volatile bool fKeepAspectRatio; volatile bool fAlwaysOnTop; @@ -105,6 +120,7 @@ public: int fSourceHeight; float fWidthScale; float fHeightScale; + int fMenuBarWidth; int fMenuBarHeight; int fControlsHeight; int fControlsWidth; @@ -112,6 +128,10 @@ public: bool fMouseDownTracking; BPoint fMouseDownMousePos; BPoint fMouseDownWindowPos; + +private: + void _AppendToPlaylist(const entry_ref& ref, + Playlist* playlist); }; #endif diff --git a/src/apps/mediaplayer/PlaybackState.h b/src/apps/mediaplayer/PlaybackState.h new file mode 100644 index 0000000000..00ebc43807 --- /dev/null +++ b/src/apps/mediaplayer/PlaybackState.h @@ -0,0 +1,17 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef PLAYBACK_STATE_H +#define PLAYBACK_STATE_H + +enum { + PLAYBACK_STATE_STOPPED = 0, + PLAYBACK_STATE_PLAYING = 1, + PLAYBACK_STATE_PAUSED = 2, +}; + +#endif // PLAYBACK_STATE_H diff --git a/src/apps/mediaplayer/Player.h b/src/apps/mediaplayer/Player.h deleted file mode 100644 index f88e8106b9..0000000000 --- a/src/apps/mediaplayer/Player.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __PLAYER_H -#define __PLAYER_H - -class Player -{ -public: - virtual void OpenFile(const entry_ref &ref) = 0; -}; - -#endif diff --git a/src/apps/mediaplayer/Playlist.cpp b/src/apps/mediaplayer/Playlist.cpp index 92a61d8738..7b36748d68 100644 --- a/src/apps/mediaplayer/Playlist.cpp +++ b/src/apps/mediaplayer/Playlist.cpp @@ -2,6 +2,7 @@ * Playlist.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,14 +18,31 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ +#include "Playlist.h" + +#include +#include #include -#include "Playlist.h" +#include + +using std::nothrow; // TODO: using BList for objects is bad, replace it with a template +Playlist::Listener::Listener() {} +Playlist::Listener::~Listener() {} +void Playlist::Listener::RefAdded(const entry_ref& ref, int32 index) {} +void Playlist::Listener::RefRemoved(int32 index) {} +void Playlist::Listener::RefsSorted() {} +void Playlist::Listener::CurrentRefChanged(int32 newIndex) {} + + +// #pragma mark - + + Playlist::Playlist() - : fList() + : fRefs() , fCurrentIndex(-1) { } @@ -33,67 +51,274 @@ Playlist::Playlist() Playlist::~Playlist() { MakeEmpty(); + + if (fListeners.CountItems() > 0) + debugger("Playlist::~Playlist() - there are still listeners attached!"); } void Playlist::MakeEmpty() { - int count = fList.CountItems(); - for (int i = count - 1; i >= 0; i--) { - entry_ref *ref = (entry_ref *)fList.RemoveItem(i); + int32 count = fRefs.CountItems(); + for (int32 i = count - 1; i >= 0; i--) { + entry_ref* ref = (entry_ref*)fRefs.RemoveItem(i); + _NotifyRefRemoved(i); delete ref; } - fCurrentIndex = -1; + SetCurrentRefIndex(-1); } -int -Playlist::CountItems() +int32 +Playlist::CountItems() const { - return fList.CountItems(); -} - - -int -Playlist::playlist_cmp(const void *p1, const void *p2) -{ - return strcasecmp((*(const entry_ref **)p1)->name, (*(const entry_ref **)p2)->name); + return fRefs.CountItems(); } void Playlist::Sort() { - fList.SortItems(playlist_cmp); + fRefs.SortItems(playlist_cmp); + _NotifyRefsSorted(); +} + + +bool +Playlist::AddRef(const entry_ref &ref) +{ + return AddRef(ref, CountItems()); +} + + +bool +Playlist::AddRef(const entry_ref &ref, int32 index) +{ + entry_ref* copy = new (nothrow) entry_ref(ref); + if (!copy) + return false; + if (!fRefs.AddItem(copy, index)) { + delete copy; + return false; + } + _NotifyRefAdded(ref, index); + return true; +} + + +bool +Playlist::AdoptPlaylist(Playlist& other) +{ + return AdoptPlaylist(other, CountItems()); +} + + +bool +Playlist::AdoptPlaylist(Playlist& other, int32 index) +{ + if (&other == this) + return false; + // NOTE: this is not intended to merge two "equal" playlists + // the given playlist is assumed to be a temporary "dummy" + if (fRefs.AddList(&other.fRefs, index)) { + // take care of the notifications + int32 count = other.fRefs.CountItems(); + for (int32 i = index; i < index + count; i++) { + entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i); + _NotifyRefAdded(*ref, i); + } + // empty the other list, so that the entry_refs are no ours + other.fRefs.MakeEmpty(); + return true; + } + return false; +} + + +entry_ref +Playlist::RemoveRef(int32 index) +{ + entry_ref _ref; + entry_ref* ref = (entry_ref*)fRefs.RemoveItem(index); + if (!ref) + return _ref; + _NotifyRefRemoved(index); + _ref = *ref; + delete ref; + return _ref; +} + + +//int32 +//Playlist::IndexOf(const entry_ref& _ref) const +//{ +// int32 count = CountItems(); +// for (int32 i = 0; i < count; i++) { +// entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i); +// if (*ref == _ref) +// return i; +// } +// return -1; +//} + + +status_t +Playlist::GetRefAt(int32 index, entry_ref* _ref) const +{ + if (!_ref) + return B_BAD_VALUE; + entry_ref* ref = (entry_ref*)fRefs.ItemAt(index); + if (!ref) + return B_BAD_INDEX; + *_ref = *ref; + + return B_OK; +} + + +//bool +//Playlist::HasRef(const entry_ref& ref) const +//{ +// return IndexOf(ref) >= 0; +//} + + + +// #pragma mark - + + +//status_t +//Playlist::NextRef(entry_ref* ref) +//{ +// int count = fRefs.CountItems(); +// if (fCurrentIndex + 2 > count) +// return B_ERROR; +// *ref = *(entry_ref*)fRefs.ItemAt(++fCurrentIndex); +// return B_OK; +//} +// +// +//status_t +//Playlist::PrevRef(entry_ref* ref) +//{ +// int count = fRefs.CountItems(); +// if (count == 0 || fCurrentIndex <= 0) +// return B_ERROR; +// *ref = *(entry_ref*)fRefs.ItemAt(--fCurrentIndex); +// return B_OK; +//} + + +void +Playlist::SetCurrentRefIndex(int32 index) +{ + if (index == fCurrentIndex) + return; + + fCurrentIndex = index; + _NotifyCurrentRefChanged(fCurrentIndex); +} + + +int32 +Playlist::CurrentRefIndex() const +{ + return fCurrentIndex; } void -Playlist::AddRef(const entry_ref &ref) +Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const { - fList.AddItem(new entry_ref(ref)); + if (canSkipPrevious) + *canSkipPrevious = fCurrentIndex > 0; + if (canSkipNext) + *canSkipNext = fCurrentIndex < CountItems() - 1; } -status_t -Playlist::NextRef(entry_ref *ref) +// pragma mark - + + +bool +Playlist::AddListener(Listener* listener) { - int count = fList.CountItems(); - if (fCurrentIndex + 2 > count) - return B_ERROR; - *ref = *(entry_ref *)fList.ItemAt(++fCurrentIndex); - return B_OK; + if (listener && !fListeners.HasItem(listener)) + return fListeners.AddItem(listener); + return false; } -status_t -Playlist::PrevRef(entry_ref *ref) +void +Playlist::RemoveListener(Listener* listener) { - int count = fList.CountItems(); - if (count == 0 || fCurrentIndex <= 0) - return B_ERROR; - *ref = *(entry_ref *)fList.ItemAt(--fCurrentIndex); - return B_OK; + fListeners.RemoveItem(listener); +} + + +// pragma mark - + + +int +Playlist::playlist_cmp(const void *p1, const void *p2) +{ + // compare complete path + + BEntry a(*(const entry_ref **)p1, false); + BEntry b(*(const entry_ref **)p2, false); + + BPath aPath(&a); + BPath bPath(&b); + + return strcmp(aPath.Path(), bPath.Path()); +} + + +void +Playlist::_NotifyRefAdded(const entry_ref& ref, int32 index) const +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->RefAdded(ref, index); + } +} + + +void +Playlist::_NotifyRefRemoved(int32 index) const +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->RefRemoved(index); + } +} + + +void +Playlist::_NotifyRefsSorted() const +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->RefsSorted(); + } +} + + +void +Playlist::_NotifyCurrentRefChanged(int32 newIndex) const +{ + BList listeners(fListeners); + int32 count = listeners.CountItems(); + for (int32 i = 0; i < count; i++) { + Listener* listener = (Listener*)listeners.ItemAtFast(i); + listener->CurrentRefChanged(newIndex); + } } diff --git a/src/apps/mediaplayer/Playlist.h b/src/apps/mediaplayer/Playlist.h index 472e610033..454992c4c7 100644 --- a/src/apps/mediaplayer/Playlist.h +++ b/src/apps/mediaplayer/Playlist.h @@ -2,6 +2,7 @@ * Playlist.h - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007 Stephan Aßmus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,28 +24,71 @@ #include #include -class Playlist -{ +class Playlist { public: - Playlist(); - ~Playlist(); + class Listener { + public: + Listener(); + virtual ~Listener(); + + virtual void RefAdded(const entry_ref& ref, int32 index); + virtual void RefRemoved(int32 index); + + virtual void RefsSorted(); + + virtual void CurrentRefChanged(int32 newIndex); + }; + +public: + Playlist(); + ~Playlist(); - void MakeEmpty(); - int CountItems(); + // list functionality + void MakeEmpty(); + int32 CountItems() const; - void Sort(); - - void AddRef(const entry_ref &ref); + void Sort(); - status_t NextRef(entry_ref *ref); - status_t PrevRef(entry_ref *ref); + bool AddRef(const entry_ref& ref); + bool AddRef(const entry_ref& ref, int32 index); + entry_ref RemoveRef(int32 index); + + bool AdoptPlaylist(Playlist& other); + bool AdoptPlaylist(Playlist& other, int32 index); + +// int32 IndexOf(const entry_ref& ref) const; + status_t GetRefAt(int32 index, entry_ref* ref) const; +// bool HasRef(const entry_ref& ref) const; + + // navigating current ref + // TODO: replace with SetCurrentRef() and listener +// status_t NextRef(entry_ref* ref); +// status_t PrevRef(entry_ref* ref); + + void SetCurrentRefIndex(int32 index); + int32 CurrentRefIndex() const; + + void GetSkipInfo(bool* canSkipPrevious, + bool* canSkipNext) const; + + // listener support + bool AddListener(Listener* listener); + void RemoveListener(Listener* listener); private: - static int playlist_cmp(const void *p1, const void *p2); + static int playlist_cmp(const void* p1, const void* p2); + + void _NotifyRefAdded(const entry_ref& ref, + int32 index) const; + void _NotifyRefRemoved(int32 index) const; + void _NotifyRefsSorted() const; + void _NotifyCurrentRefChanged(int32 newIndex) const; private: - BList fList; - int fCurrentIndex; + BList fRefs; + BList fListeners; + + int32 fCurrentIndex; }; #endif diff --git a/src/apps/mediaplayer/PlaylistObserver.cpp b/src/apps/mediaplayer/PlaylistObserver.cpp new file mode 100644 index 0000000000..33029e6039 --- /dev/null +++ b/src/apps/mediaplayer/PlaylistObserver.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "PlaylistObserver.h" + +#include + + +PlaylistObserver::PlaylistObserver(BHandler* target) + : Playlist::Listener() + , AbstractLOAdapter(target) +{ +} + + +PlaylistObserver::~PlaylistObserver() +{ +} + + +void +PlaylistObserver::RefAdded(const entry_ref& ref, int32 index) +{ + BMessage message(MSG_PLAYLIST_REF_ADDED); + message.AddRef("refs", &ref); + message.AddInt32("index", index); + + DeliverMessage(message); +} + + +void +PlaylistObserver::RefRemoved(int32 index) +{ + BMessage message(MSG_PLAYLIST_REF_REMOVED); + message.AddInt32("index", index); + + DeliverMessage(message); +} + + +void +PlaylistObserver::RefsSorted() +{ + BMessage message(MSG_PLAYLIST_REFS_SORTED); + + DeliverMessage(message); +} + + +void +PlaylistObserver::CurrentRefChanged(int32 newIndex) +{ + BMessage message(MSG_PLAYLIST_CURRENT_REF_CHANGED); + message.AddInt32("index", newIndex); + + DeliverMessage(message); +} + diff --git a/src/apps/mediaplayer/PlaylistObserver.h b/src/apps/mediaplayer/PlaylistObserver.h new file mode 100644 index 0000000000..a34d5edfd7 --- /dev/null +++ b/src/apps/mediaplayer/PlaylistObserver.h @@ -0,0 +1,35 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef PLAYLIST_OBSERVER_H +#define PLAYLIST_OBSERVER_H + +#include "AbstractLOAdapter.h" +#include "Playlist.h" + +enum { + MSG_PLAYLIST_REF_ADDED = 'plra', + MSG_PLAYLIST_REF_REMOVED = 'plrr', + MSG_PLAYLIST_REFS_SORTED = 'plrs', + MSG_PLAYLIST_CURRENT_REF_CHANGED = 'plcc' +}; + +class PlaylistObserver : public Playlist::Listener, + public AbstractLOAdapter { + public: + PlaylistObserver(BHandler* target); + virtual ~PlaylistObserver(); + + virtual void RefAdded(const entry_ref& ref, int32 index); + virtual void RefRemoved(int32 index); + + virtual void RefsSorted(); + + virtual void CurrentRefChanged(int32 newIndex); +}; + +#endif // PLAYLIST_OBSERVER_H diff --git a/src/apps/mediaplayer/PlaylistWindow.cpp b/src/apps/mediaplayer/PlaylistWindow.cpp new file mode 100644 index 0000000000..c499b08b51 --- /dev/null +++ b/src/apps/mediaplayer/PlaylistWindow.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "PlaylistWindow.h" + +#include +#include + +#include "ListViews.h" +#include "Playlist.h" +#include "PlaylistObserver.h" + +class PlaylistListView : public SimpleListView { + public: + PlaylistListView(BRect frame, + Playlist* playlist); + virtual ~PlaylistListView(); + + // BView interface + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage* message); + + // SimpleListView interface + virtual void MoveItems(BList& items, int32 toIndex); + virtual void CopyItems(BList& items, int32 toIndex); + virtual void RemoveItemList(BList& indices); + + private: + void _FullSync(); + void _AddItem(const entry_ref& ref, int32 index); + void _RemoveItem(int32 index); + + Playlist* fPlaylist; + PlaylistObserver* fPlaylistObserver; +}; + + +// #pragma mark - + + +PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist) + : SimpleListView(frame, "playlist listview", NULL) + , fPlaylist(playlist) + , fPlaylistObserver(new PlaylistObserver(this)) +{ + fPlaylist->AddListener(fPlaylistObserver); +} + + +PlaylistListView::~PlaylistListView() +{ + fPlaylist->RemoveListener(fPlaylistObserver); + delete fPlaylistObserver; +} + + +void +PlaylistListView::AttachedToWindow() +{ + _FullSync(); + SimpleListView::AttachedToWindow(); +} + + +void +PlaylistListView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_PLAYLIST_REF_ADDED: { + entry_ref ref; + int32 index; + if (message->FindRef("refs", &ref) == B_OK + && message->FindInt32("index", &index) == B_OK) + _AddItem(ref, index); + break; + } + case MSG_PLAYLIST_REF_REMOVED: { + int32 index; + if (message->FindInt32("index", &index) == B_OK) + _RemoveItem(index); + break; + } + case MSG_PLAYLIST_REFS_SORTED: + _FullSync(); + break; + default: + SimpleListView::MessageReceived(message); + break; + } +} + + +void +PlaylistListView::MoveItems(BList& items, int32 toIndex) +{ + // TODO: drag indices instead of items! (avoids IndexOf()) + int32 count = items.CountItems(); + for (int32 i = 0; i < count; i++) { + BListItem* item = (BListItem*)items.ItemAtFast(i); + int32 index = IndexOf(item); + entry_ref ref = fPlaylist->RemoveRef(index); + } +} + + +void +PlaylistListView::CopyItems(BList& items, int32 toIndex) +{ +} + + +void +PlaylistListView::RemoveItemList(BList& indices) +{ + int32 count = indices.CountItems(); + for (int32 i = 0; i < count; i++) { + int32 index = (int32)indices.ItemAtFast(i); + fPlaylist->RemoveRef(index); + } +} + + +void +PlaylistListView::_FullSync() +{ +// BScrollBar* scrollBar = ScrollBar(B_VERTICAL); +// SetScrollBar(); +// + MakeEmpty(); + + int32 count = fPlaylist->CountItems(); + for (int32 i = 0; i < count; i++) { + entry_ref ref; + if (fPlaylist->GetRefAt(i, &ref) == B_OK) + _AddItem(ref, i); + } +} + + +void +PlaylistListView::_AddItem(const entry_ref& ref, int32 index) +{ + SimpleItem* item = new SimpleItem(ref.name); + AddItem(item, index); +} + + +void +PlaylistListView::_RemoveItem(int32 index) +{ + delete RemoveItem(index); +} + + +// #pragma mark - + + +PlaylistWindow::PlaylistWindow(BRect frame, Playlist* playlist) + : BWindow(frame, "Playlist", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS) +{ + frame = Bounds(); + frame.right -= B_V_SCROLL_BAR_WIDTH; + PlaylistListView* listView = new PlaylistListView(frame, playlist); + + fTopView = new BScrollView("playlist scrollview", + listView, B_FOLLOW_ALL, 0, false, true, B_NO_BORDER); + + AddChild(fTopView); +} + + +PlaylistWindow::~PlaylistWindow() +{ + // give listeners a chance to detach themselves + fTopView->RemoveSelf(); + delete fTopView; +} + + +bool +PlaylistWindow::QuitRequested() +{ + Hide(); + return false; +} + diff --git a/src/apps/mediaplayer/PlaylistWindow.h b/src/apps/mediaplayer/PlaylistWindow.h new file mode 100644 index 0000000000..99aa1a3b50 --- /dev/null +++ b/src/apps/mediaplayer/PlaylistWindow.h @@ -0,0 +1,27 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef PLAYLIST_WINDOW_H +#define PLAYLIST_WINDOW_H + +#include + +class Playlist; + +class PlaylistWindow : public BWindow { + public: + PlaylistWindow(BRect frame, + Playlist* playlist); + virtual ~PlaylistWindow(); + + virtual bool QuitRequested(); + + private: + BView* fTopView; +}; + +#endif // PLAYLIST_WINDOW_H diff --git a/src/apps/mediaplayer/SeekSlider.cpp b/src/apps/mediaplayer/SeekSlider.cpp index 80a33a7824..308860de4a 100644 --- a/src/apps/mediaplayer/SeekSlider.cpp +++ b/src/apps/mediaplayer/SeekSlider.cpp @@ -32,11 +32,12 @@ const char* kDisabledSeekMessage = "Drop files to play"; SeekSlider::SeekSlider(BRect frame, const char* name, BMessage* message, int32 minValue, int32 maxValue) : BControl(frame, name, NULL, message, B_FOLLOW_NONE, - B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), - fTracking(false), - fKnobPos(_KnobPosFor(Bounds(), Value())), - fMinValue(minValue), - fMaxValue(maxValue) + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE) + , fTracking(false) + , fLastTrackTime(0) + , fKnobPos(_KnobPosFor(Bounds(), Value())) + , fMinValue(minValue) + , fMaxValue(maxValue) { BFont font(be_plain_font); font.SetSize(9.0); @@ -67,26 +68,13 @@ SeekSlider::SetValue(int32 value) BControl::SetValueNoUpdate(value); #else BControl::SetValue(value); + // this will Invalidate() the whole view #endif Invoke(); -#if __HAIKU__ - int32 oldKnobPos = fKnobPos; - fKnobPos = _KnobPosFor(Bounds(), Value()); - // invalidate - if (oldKnobPos != fKnobPos) { - float knobWidth2 = SEEK_SLIDER_KNOB_WIDTH / 2.0; - BRect oldKnob(Bounds()); - BRect newKnob(oldKnob); - oldKnob.left = oldKnobPos - knobWidth2; - oldKnob.right = oldKnobPos + knobWidth2; - newKnob.left = fKnobPos - knobWidth2; - newKnob.right = fKnobPos + knobWidth2; - Invalidate(oldKnob | newKnob); - } -#else - fKnobPos = _KnobPosFor(Bounds(), Value()); -#endif + _SetKnobPosition(_KnobPosFor(Bounds(), Value())); + + fLastTrackTime = system_time(); } @@ -138,7 +126,7 @@ SeekSlider::Draw(BRect updateRect) float sliderStart = (r.left + knobWidth2); for (int32 i = 0; i < dotCount; i++) { - dotPos.x = sliderStart + i * 6.0 + 5.0; + dotPos.x = sliderStart + i * 6.0; StrokeLine(dotPos, BPoint(dotPos.x, dotPos.y + 6.0)); } // slider handle @@ -298,11 +286,20 @@ SeekSlider::SetPosition(float position) int32 value = fMinValue + (int32)floorf((fMaxValue - fMinValue) * position + 0.5); if (value != Value()) { BControl::SetValue(value); - + _SetKnobPosition(_KnobPosFor(Bounds(), Value())); } } +bool +SeekSlider::IsTracking() const +{ + if (fTracking) + return true; + return system_time() - fLastTrackTime < 250000; +} + + // #pragma mark - @@ -349,5 +346,22 @@ SeekSlider::_StrokeFrame(BRect r, rgb_color left, rgb_color top, } +void +SeekSlider::_SetKnobPosition(int32 knobPos) +{ + if (fKnobPos == knobPos) + return; + float knobWidth2 = SEEK_SLIDER_KNOB_WIDTH / 2.0; + BRect oldKnob(Bounds()); + BRect newKnob(oldKnob); + oldKnob.left = fKnobPos - knobWidth2; + oldKnob.right = fKnobPos + knobWidth2; + + fKnobPos = knobPos; + + newKnob.left = fKnobPos - knobWidth2; + newKnob.right = fKnobPos + knobWidth2; + Invalidate(oldKnob | newKnob); +} diff --git a/src/apps/mediaplayer/SeekSlider.h b/src/apps/mediaplayer/SeekSlider.h index a24312cf8d..48148ec2b3 100644 --- a/src/apps/mediaplayer/SeekSlider.h +++ b/src/apps/mediaplayer/SeekSlider.h @@ -34,6 +34,7 @@ class SeekSlider : public BControl { // SeekSlider void SetPosition(float position); + bool IsTracking() const; private: int32 _ValueFor(float x) const; @@ -44,8 +45,11 @@ private: rgb_color top, rgb_color right, rgb_color bottom); + void _SetKnobPosition(int32 knobPos); + bool fTracking; + bigtime_t fLastTrackTime; int32 fKnobPos; int32 fMinValue; int32 fMaxValue; diff --git a/src/apps/mediaplayer/SoundOutput.cpp b/src/apps/mediaplayer/SoundOutput.cpp index 6b77e1bcfc..bbf12b3132 100644 --- a/src/apps/mediaplayer/SoundOutput.cpp +++ b/src/apps/mediaplayer/SoundOutput.cpp @@ -61,7 +61,14 @@ SoundOutput::InitCheck() return B_ERROR; return fSoundPlayer->InitCheck(); } - + + +media_raw_audio_format +SoundOutput::Format() const +{ + return fSoundPlayer->Format(); +} + bigtime_t SoundOutput::Latency() diff --git a/src/apps/mediaplayer/SoundOutput.h b/src/apps/mediaplayer/SoundOutput.h index 197c1c8726..260e5bb1ed 100644 --- a/src/apps/mediaplayer/SoundOutput.h +++ b/src/apps/mediaplayer/SoundOutput.h @@ -32,13 +32,15 @@ public: status_t InitCheck(); + media_raw_audio_format Format() const; + bigtime_t Latency(); float Volume(); void SetVolume(float new_volume); void Play(const void *data, size_t size); - + private: static void play_buffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format & format); void PlayBuffer(void *buffer); diff --git a/src/apps/mediaplayer/TransportControlGroup.cpp b/src/apps/mediaplayer/TransportControlGroup.cpp index b7facb5ca8..4e7901222f 100644 --- a/src/apps/mediaplayer/TransportControlGroup.cpp +++ b/src/apps/mediaplayer/TransportControlGroup.cpp @@ -18,8 +18,9 @@ #include #include "ButtonBitmaps.h" -#include "TransportButton.h" +#include "PlaybackState.h" #include "SeekSlider.h" +#include "TransportButton.h" #include "VolumeSlider.h" enum { @@ -51,11 +52,12 @@ enum { #define kPositionFactor 3000 // constructor -TransportControlGroup::TransportControlGroup(BRect frame) +TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons, + bool useWindButtons) : BView(frame, "transport control group", B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, - B_WILL_DRAW | B_FRAME_EVENTS), - fBottomControlHeight(0.0) + B_WILL_DRAW | B_FRAME_EVENTS) + , fBottomControlHeight(0.0) { SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); @@ -69,40 +71,50 @@ TransportControlGroup::TransportControlGroup(BRect frame) AddChild(fSeekSlider); // Buttons - // Skip Back - frame.right = kRewindBitmapWidth - 1; - frame.bottom = kRewindBitmapHeight - 1; - fBottomControlHeight = kRewindBitmapHeight - 1.0; - fSkipBack = new TransportButton(frame, B_EMPTY_STRING, - kSkipBackBitmapBits, - kPressedSkipBackBitmapBits, - kDisabledSkipBackBitmapBits, - new BMessage(MSG_SKIP_BACKWARDS)); - AddChild(fSkipBack); + if (useSkipButtons) { + // Skip Back + frame.right = kRewindBitmapWidth - 1; + frame.bottom = kRewindBitmapHeight - 1; + fBottomControlHeight = kRewindBitmapHeight - 1.0; + fSkipBack = new TransportButton(frame, B_EMPTY_STRING, + kSkipBackBitmapBits, + kPressedSkipBackBitmapBits, + kDisabledSkipBackBitmapBits, + new BMessage(MSG_SKIP_BACKWARDS)); + AddChild(fSkipBack); + + // Skip Foward + fSkipForward = new TransportButton(frame, B_EMPTY_STRING, + kSkipForwardBitmapBits, + kPressedSkipForwardBitmapBits, + kDisabledSkipForwardBitmapBits, + new BMessage(MSG_SKIP_FORWARD)); + AddChild(fSkipForward); + } else { + fSkipBack = NULL; + fSkipForward = NULL; + } - // Skip Foward - fSkipForward = new TransportButton(frame, B_EMPTY_STRING, - kSkipForwardBitmapBits, - kPressedSkipForwardBitmapBits, - kDisabledSkipForwardBitmapBits, - new BMessage(MSG_SKIP_FORWARD)); - AddChild(fSkipForward); - - // Forward - fForward = new TransportButton(frame, B_EMPTY_STRING, - kForwardBitmapBits, - kPressedForwardBitmapBits, - kDisabledForwardBitmapBits, - new BMessage(MSG_FORWARD)); - AddChild(fForward); - - // Rewind - fRewind = new TransportButton(frame, B_EMPTY_STRING, - kRewindBitmapBits, - kPressedRewindBitmapBits, - kDisabledRewindBitmapBits, - new BMessage(MSG_REWIND)); - AddChild(fRewind); + if (useWindButtons) { + // Forward + fForward = new TransportButton(frame, B_EMPTY_STRING, + kForwardBitmapBits, + kPressedForwardBitmapBits, + kDisabledForwardBitmapBits, + new BMessage(MSG_FORWARD)); + AddChild(fForward); + + // Rewind + fRewind = new TransportButton(frame, B_EMPTY_STRING, + kRewindBitmapBits, + kPressedRewindBitmapBits, + kDisabledRewindBitmapBits, + new BMessage(MSG_REWIND)); + AddChild(fRewind); + } else { + fForward = NULL; + fRewind = NULL; + } // Play Pause frame.right = kPlayPauseBitmapWidth - 1; @@ -171,10 +183,14 @@ TransportControlGroup::AttachedToWindow() // we are now a valid BHandler fSeekSlider->SetTarget(this); fVolumeSlider->SetTarget(this); - fSkipBack->SetTarget(this); - fSkipForward->SetTarget(this); - fRewind->SetTarget(this); - fForward->SetTarget(this); + if (fSkipBack) + fSkipBack->SetTarget(this); + if (fSkipForward) + fSkipForward->SetTarget(this); + if (fRewind) + fRewind->SetTarget(this); + if (fForward) + fForward->SetTarget(this); fPlayPause->SetTarget(this); fStop->SetTarget(this); fMute->SetTarget(this); @@ -320,21 +336,26 @@ TransportControlGroup::_GainToDb(float gain) void TransportControlGroup::SetEnabled(uint32 buttons) { -// if (B_OK != LockLooperWithTimeout(50000)) if (!LockLooper()) return; + fSeekSlider->SetEnabled(buttons & SEEK_ENABLED); fVolumeSlider->SetEnabled(buttons & VOLUME_ENABLED); fMute->SetEnabled(buttons & VOLUME_ENABLED); - fSkipBack->SetEnabled(buttons & SKIP_BACK_ENABLED); - fSkipForward->SetEnabled(buttons & SKIP_FORWARD_ENABLED); - fRewind->SetEnabled(buttons & SEEK_BACK_ENABLED); - fForward->SetEnabled(buttons & SEEK_FORWARD_ENABLED); + if (fSkipBack) + fSkipBack->SetEnabled(buttons & SKIP_BACK_ENABLED); + if (fSkipForward) + fSkipForward->SetEnabled(buttons & SKIP_FORWARD_ENABLED); + if (fRewind) + fRewind->SetEnabled(buttons & SEEK_BACK_ENABLED); + if (fForward) + fForward->SetEnabled(buttons & SEEK_FORWARD_ENABLED); fPlayPause->SetEnabled(buttons & PLAYBACK_ENABLED); fStop->SetEnabled(buttons & PLAYBACK_ENABLED); + UnlockLooper(); } @@ -344,9 +365,9 @@ TransportControlGroup::SetEnabled(uint32 buttons) void TransportControlGroup::SetPlaybackState(uint32 state) { -// if (B_OK != LockLooperWithTimeout(50000)) if (!LockLooper()) return; + switch (state) { case PLAYBACK_STATE_PLAYING: fPlayPause->SetPlaying(); @@ -358,6 +379,7 @@ TransportControlGroup::SetPlaybackState(uint32 state) fPlayPause->SetStopped(); break; } + UnlockLooper(); } @@ -365,11 +387,14 @@ TransportControlGroup::SetPlaybackState(uint32 state) void TransportControlGroup::SetSkippable(bool backward, bool forward) { -// if (B_OK != LockLooperWithTimeout(50000)) if (!LockLooper()) return; - fSkipBack->SetEnabled(backward); - fSkipForward->SetEnabled(forward); + + if (fSkipBack) + fSkipBack->SetEnabled(backward); + if (fSkipForward) + fSkipForward->SetEnabled(forward); + UnlockLooper(); } @@ -379,11 +404,12 @@ TransportControlGroup::SetSkippable(bool backward, bool forward) void TransportControlGroup::SetAudioEnabled(bool enabled) { -// if (B_OK != LockLooperWithTimeout(50000)) if (!LockLooper()) return; + fMute->SetEnabled(enabled); fVolumeSlider->SetEnabled(enabled); + UnlockLooper(); } @@ -391,10 +417,11 @@ TransportControlGroup::SetAudioEnabled(bool enabled) void TransportControlGroup::SetMuted(bool mute) { -// if (B_OK != LockLooperWithTimeout(50000)) if (!LockLooper()) return; + fVolumeSlider->SetMuted(mute); + UnlockLooper(); } @@ -404,7 +431,10 @@ TransportControlGroup::SetVolume(float value) { if (B_OK != LockLooperWithTimeout(50000)) return; - fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(_GainToDb(value))) * kVolumeFactor); + + fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear( + _GainToDb(value))) * kVolumeFactor); + UnlockLooper(); } @@ -412,10 +442,10 @@ TransportControlGroup::SetVolume(float value) void TransportControlGroup::SetPosition(float value) { - if (B_OK != LockLooperWithTimeout(50000)) + if (fSeekSlider->IsTracking()) return; + fSeekSlider->SetPosition(value); - UnlockLooper(); } @@ -427,12 +457,17 @@ TransportControlGroup::_LayoutControls(BRect frame) const { BRect r(frame); // calculate absolutly minimal width - float minWidth = fSkipBack->Bounds().Width(); - minWidth += fRewind->Bounds().Width(); + float minWidth = 0.0; + if (fSkipBack) + minWidth += fSkipBack->Bounds().Width(); + if (fRewind) + minWidth += fRewind->Bounds().Width(); minWidth += fStop->Bounds().Width(); minWidth += fPlayPause->Bounds().Width(); - minWidth += fForward->Bounds().Width(); - minWidth += fSkipForward->Bounds().Width(); + if (fForward) + minWidth += fForward->Bounds().Width(); + if (fSkipForward) + minWidth += fSkipForward->Bounds().Width(); minWidth += fMute->Bounds().Width(); minWidth += VOLUME_MIN_WIDTH; @@ -449,28 +484,37 @@ TransportControlGroup::_LayoutControls(BRect frame) const r.top = r.bottom + MIN_SPACE + 1.0; r.bottom = frame.bottom; // skip back - r.right = r.left + fSkipBack->Bounds().Width(); - _LayoutControl(fSkipBack, r); + if (fSkipBack) { + r.right = r.left + fSkipBack->Bounds().Width(); + _LayoutControl(fSkipBack, r); + r.left = r.right + space; + } // rewind - r.left = r.right + space; - r.right = r.left + fRewind->Bounds().Width(); - _LayoutControl(fRewind, r); + if (fRewind) { + r.right = r.left + fRewind->Bounds().Width(); + _LayoutControl(fRewind, r); + r.left = r.right + space; + } // stop - r.left = r.right + space; r.right = r.left + fStop->Bounds().Width(); _LayoutControl(fStop, r); - // play/pause r.left = r.right + space; + // play/pause r.right = r.left + fPlayPause->Bounds().Width(); _LayoutControl(fPlayPause, r); + r.left = r.right + space; // forward - r.left = r.right + space; - r.right = r.left + fForward->Bounds().Width(); - _LayoutControl(fForward, r); + if (fForward) { + r.right = r.left + fForward->Bounds().Width(); + _LayoutControl(fForward, r); + r.left = r.right + space; + } // skip forward - r.left = r.right + space; - r.right = r.left + fSkipForward->Bounds().Width(); - _LayoutControl(fSkipForward, r); + if (fSkipForward) { + r.right = r.left + fSkipForward->Bounds().Width(); + _LayoutControl(fSkipForward, r); + r.left = r.right + space; + } // speaker icon r.left = r.right + space + space; r.right = r.left + fMute->Bounds().Width(); @@ -488,12 +532,16 @@ TransportControlGroup::_MinFrame() const { // add up width of controls along bottom (seek slider will likely adopt) float minWidth = 2 * BORDER_INSET; - minWidth += fSkipBack->Bounds().Width() + MIN_SPACE; - minWidth += fRewind->Bounds().Width() + MIN_SPACE; + if (fSkipBack) + minWidth += fSkipBack->Bounds().Width() + MIN_SPACE; + if (fRewind) + minWidth += fRewind->Bounds().Width() + MIN_SPACE; minWidth += fStop->Bounds().Width() + MIN_SPACE; minWidth += fPlayPause->Bounds().Width() + MIN_SPACE; - minWidth += fForward->Bounds().Width() + MIN_SPACE; - minWidth += fSkipForward->Bounds().Width() + MIN_SPACE + MIN_SPACE; + if (fForward) + minWidth += fForward->Bounds().Width() + MIN_SPACE; + if (fSkipForward) + minWidth += fSkipForward->Bounds().Width() + MIN_SPACE + MIN_SPACE; minWidth += fMute->Bounds().Width() + SPEAKER_SLIDER_DIST; minWidth += VOLUME_MIN_WIDTH; diff --git a/src/apps/mediaplayer/TransportControlGroup.h b/src/apps/mediaplayer/TransportControlGroup.h index 0f1b880114..d45c1a4660 100644 --- a/src/apps/mediaplayer/TransportControlGroup.h +++ b/src/apps/mediaplayer/TransportControlGroup.h @@ -7,7 +7,7 @@ */ // NOTE: Based on my code in the BeOS interface for the VLC media player -// that I did during the VLC 0.4.3 - 0.4.6 times. Code not done by me +// that I did during the VLC 0.4.3 - 0.4.6 times. Code not written by me // removed. -Stephan Aßmus #ifndef TRANSPORT_CONTROL_GROUP_H @@ -20,12 +20,6 @@ class TransportButton; class SeekSlider; class VolumeSlider; -enum { - PLAYBACK_STATE_PLAYING = 0, - PLAYBACK_STATE_PAUSED, - PLAYBACK_STATE_STOPPED, -}; - enum { SKIP_BACK_ENABLED = 1 << 0, SEEK_BACK_ENABLED = 1 << 1, @@ -38,7 +32,9 @@ enum { class TransportControlGroup : public BView { public: - TransportControlGroup(BRect frame); + TransportControlGroup(BRect frame, + bool useSkipButtons = true, + bool useWindButtons = false); virtual ~TransportControlGroup(); // BView interface @@ -106,7 +102,6 @@ class TransportControlGroup : public BView { TransportButton* fStop; TransportButton* fMute; - int fCurrentStatus; float fBottomControlHeight; }; diff --git a/src/apps/mediaplayer/VideoView.cpp b/src/apps/mediaplayer/VideoView.cpp index 99ba05defc..ad70a55783 100644 --- a/src/apps/mediaplayer/VideoView.cpp +++ b/src/apps/mediaplayer/VideoView.cpp @@ -161,37 +161,54 @@ VideoView::DrawFrame() { // printf("VideoView::DrawFrame\n"); - bool want_overlay = fController->IsOverlayActive(); + if (LockLooperWithTimeout(50000) != B_OK) + return; - if (!want_overlay && fOverlayActive) { - if (LockLooperWithTimeout(50000) == B_OK) { - ClearViewOverlay(); - UnlockLooper(); - fOverlayActive = false; + fController->LockBitmap(); + BBitmap *bmp = fController->Bitmap(); + + if (bmp) { + bool want_overlay = bmp->ColorSpace() == B_YCbCr422; + + if (!want_overlay && fOverlayActive) { + if (LockLooperWithTimeout(50000) == B_OK) { + ClearViewOverlay(); + UnlockLooper(); + fOverlayActive = false; + } else { + printf("can't ClearViewOverlay, as LockLooperWithTimeout failed\n"); + return; + } + } + + if (want_overlay && !fOverlayActive ) { +printf("trying to activate overlay..."); + // reserve overlay channel + status_t ret = SetViewOverlay(bmp, bmp->Bounds(), Bounds(), + &fOverlayKeyColor, B_FOLLOW_ALL, + B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL); + if (ret == B_OK) { +printf("success\n"); + fOverlayActive = true; + Invalidate(); + } else { +printf("failed: %s\n", strerror(ret)); + } + } else if (fOverlayActive) { + // transfer overlay channel + rgb_color overlayKey; + SetViewOverlay(bmp, bmp->Bounds(), Bounds(), &overlayKey, + B_FOLLOW_ALL, B_OVERLAY_TRANSFER_CHANNEL + | B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL); } else { - printf("can't ClearViewOverlay, as LockLooperWithTimeout failed\n"); + // no overlay + DrawBitmap(bmp, Bounds()); } } - if (want_overlay && !fOverlayActive ) { - fController->LockBitmap(); - BBitmap *bmp = fController->Bitmap(); - if (bmp && LockLooperWithTimeout(50000) == B_OK) { - SetViewOverlay(bmp, bmp->Bounds(), Bounds(), &fOverlayKeyColor, - B_FOLLOW_ALL, - /*B_OVERLAY_TRANSFER_CHANNEL | */B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL ); - fOverlayActive = true; - Invalidate(); - UnlockLooper(); - } - fController->UnlockBitmap(); - } - if (!fOverlayActive) { - if (LockLooperWithTimeout(50000) != B_OK) - return; - Invalidate(); - UnlockLooper(); - } + fController->UnlockBitmap(); + + UnlockLooper(); } diff --git a/src/apps/mediaplayer/VideoView.h b/src/apps/mediaplayer/VideoView.h index fcfb99fd6c..7ff789369e 100644 --- a/src/apps/mediaplayer/VideoView.h +++ b/src/apps/mediaplayer/VideoView.h @@ -51,7 +51,7 @@ private: private: Controller * fController; - bool fOverlayActive; + volatile bool fOverlayActive; rgb_color fOverlayKeyColor; }; diff --git a/src/apps/mediaplayer/supplier/AudioSupplier.cpp b/src/apps/mediaplayer/supplier/AudioSupplier.cpp new file mode 100644 index 0000000000..7fdd149bee --- /dev/null +++ b/src/apps/mediaplayer/supplier/AudioSupplier.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "AudioSupplier.h" + + +AudioSupplier::AudioSupplier() +{ +} + + +AudioSupplier::~AudioSupplier() +{ +} + diff --git a/src/apps/mediaplayer/supplier/AudioSupplier.h b/src/apps/mediaplayer/supplier/AudioSupplier.h new file mode 100644 index 0000000000..57a7246064 --- /dev/null +++ b/src/apps/mediaplayer/supplier/AudioSupplier.h @@ -0,0 +1,27 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef AUDIO_SUPPLIER_H +#define AUDIO_SUPPLIER_H + +#include + +class AudioSupplier { + public: + AudioSupplier(); + virtual ~AudioSupplier(); + + virtual media_format Format() const = 0; + virtual status_t ReadFrames(void* buffer, int64* framesRead, + bigtime_t* performanceTime) = 0; + virtual status_t SeekToTime(bigtime_t* performanceTime) = 0; + + virtual bigtime_t Position() const = 0; + virtual bigtime_t Duration() const = 0; +}; + +#endif // AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp new file mode 100644 index 0000000000..d606eb7588 --- /dev/null +++ b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "MediaTrackAudioSupplier.h" + +#include +#include +#include + +#include + +using std::nothrow; + +// constructor +MediaTrackAudioSupplier::MediaTrackAudioSupplier(BMediaTrack* track) + : AudioSupplier() + , fMediaTrack(track) + + , fPerformanceTime(0) + , fDuration(0) +{ + if (!fMediaTrack) { + printf("MediaTrackAudioSupplier() - no media track\n"); + return; + } + + fFormat.u.raw_audio = media_multi_audio_format::wildcard; + #ifdef __HAIKU__ + fFormat.u.raw_audio.format = media_multi_audio_format::B_AUDIO_FLOAT; + #endif + status_t ret = fMediaTrack->DecodedFormat(&fFormat); + if (ret < B_OK) { + printf("MediaTrackAudioSupplier() - " + "fMediaTrack->DecodedFormat(): %s\n", strerror(ret)); + return; + } + + fDuration = fMediaTrack->Duration(); +// +// for (bigtime_t time = 0; time < fDuration; time += 10000) { +// bigtime_t keyFrameTime = time; +// fMediaTrack->FindKeyFrameForTime(&keyFrameTime, +// B_MEDIA_SEEK_CLOSEST_BACKWARD); +// printf("audio keyframe time for time: %lld -> %lld\n", time, keyFrameTime); +// } +} + +// destructor +MediaTrackAudioSupplier::~MediaTrackAudioSupplier() +{ +} + + +media_format +MediaTrackAudioSupplier::Format() const +{ + return fFormat; +} + + +status_t +MediaTrackAudioSupplier::ReadFrames(void* buffer, int64* framesRead, + bigtime_t* performanceTime) +{ + if (!fMediaTrack) + return B_NO_INIT; + if (!buffer || !framesRead) + return B_BAD_VALUE; + + media_header mediaHeader; + status_t ret = fMediaTrack->ReadFrames(buffer, framesRead, &mediaHeader); + + if (ret < B_OK) { + printf("MediaTrackAudioSupplier::ReadFrame() - " + "error while reading frames: %s\n", strerror(ret)); + } else { + fPerformanceTime = mediaHeader.start_time; + } + + if (performanceTime) + *performanceTime = fPerformanceTime; + + return ret; +} + + +status_t +MediaTrackAudioSupplier::SeekToTime(bigtime_t* performanceTime) +{ + if (!fMediaTrack) + return B_NO_INIT; + +bigtime_t _performanceTime = *performanceTime; + status_t ret = fMediaTrack->SeekToTime(performanceTime); + if (ret == B_OK) { +printf("seeked: %lld -> %lld\n", _performanceTime, *performanceTime); + fPerformanceTime = *performanceTime; + } + + return ret; +} + diff --git a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h new file mode 100644 index 0000000000..2f9f3fb279 --- /dev/null +++ b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h @@ -0,0 +1,38 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef MEDIA_TRACK_AUDIO_SUPPLIER_H +#define MEDIA_TRACK_AUDIO_SUPPLIER_H + +#include "AudioSupplier.h" + +class BMediaTrack; + +class MediaTrackAudioSupplier : public AudioSupplier { + public: + MediaTrackAudioSupplier(BMediaTrack* track); + virtual ~MediaTrackAudioSupplier(); + + virtual media_format Format() const; + virtual status_t ReadFrames(void* buffer, int64* framesRead, + bigtime_t* performanceTime); + virtual status_t SeekToTime(bigtime_t* performanceTime); + + virtual bigtime_t Position() const + { return fPerformanceTime; } + virtual bigtime_t Duration() const + { return fDuration; } + private: + BMediaTrack* fMediaTrack; + + media_format fFormat; + + bigtime_t fPerformanceTime; + bigtime_t fDuration; +}; + +#endif // MEDIA_TRACK_AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp new file mode 100644 index 0000000000..700d622472 --- /dev/null +++ b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp @@ -0,0 +1,281 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "MediaTrackVideoSupplier.h" + +#include +#include +#include + +#include + +using std::nothrow; + +#define DEBUG_DECODED_FRAME 0 +#if DEBUG_DECODED_FRAME +# include +# include +# include +# include +#endif // DEBUG_DECODED_FRAME + +static const char* string_for_color_space(color_space format); + + +// constructor +MediaTrackVideoSupplier::MediaTrackVideoSupplier(BMediaTrack* track, + color_space format) + : VideoSupplier() + , fVideoTrack(track) + + , fPerformanceTime(0) + , fDuration(0) +{ + if (!fVideoTrack) { + printf("MediaTrackVideoSupplier() - no video track\n"); + return; + } + + // get the encoded format + memset(&fFormat, 0, sizeof(media_format)); + status_t ret = fVideoTrack->EncodedFormat(&fFormat); + if (ret < B_OK) { + printf("MediaTrackVideoSupplier::InitCheck() - " + "fVideoTrack->EncodedFormat(): %s\n", strerror(ret)); + return; + } + + // get ouput video frame size + uint32 width = fFormat.u.encoded_video.output.display.line_width; + uint32 height = fFormat.u.encoded_video.output.display.line_count; + + // specifiy the decoded format. we derive this information from + // the encoded format (width & height). + memset(&fFormat, 0, sizeof(media_format)); +// fFormat.u.raw_video.last_active = height - 1; +// fFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; +// fFormat.u.raw_video.pixel_width_aspect = 1; +// fFormat.u.raw_video.pixel_height_aspect = 1; + fFormat.u.raw_video.display.format = format; + fFormat.u.raw_video.display.line_width = width; + fFormat.u.raw_video.display.line_count = height; + if (format == B_RGB32 || format == B_RGBA32) + fFormat.u.raw_video.display.bytes_per_row = width * 4; + else if (format == B_YCbCr422) + fFormat.u.raw_video.display.bytes_per_row = ((width * 2 + 3) / 4) * 4; + + ret = fVideoTrack->DecodedFormat(&fFormat); + + if (ret < B_OK) { + printf("MediaTrackVideoSupplier() - " + "fVideoTrack->DecodedFormat(): %s\n", strerror(ret)); + return; + } + + if (fFormat.u.raw_video.display.format != format) { + printf("MediaTrackVideoSupplier() - " + " codec changed colorspace of decoded format (%s -> %s)!\n" + " this is bad for performance, since colorspace conversion\n" + " needs to happen during playback.\n", + string_for_color_space(format), + string_for_color_space(fFormat.u.raw_video.display.format)); + // check if the codec forgot to adjust bytes_per_row + uint32 minBPR; + format = fFormat.u.raw_video.display.format; + if (format == B_YCbCr422) + minBPR = ((width * 2 + 3) / 4) * 4; + else + minBPR = width * 4; + if (minBPR != fFormat.u.raw_video.display.bytes_per_row) { + printf(" -> stupid codec forgot to adjust bytes_per_row!\n"); + fFormat.u.raw_video.display.bytes_per_row = minBPR; + fVideoTrack->DecodedFormat(&fFormat); + } + } + + fDuration = fVideoTrack->Duration(); + +// for (bigtime_t time = 0; time < fDuration; time += 10000) { +// bigtime_t keyFrameTime = time; +// fVideoTrack->FindKeyFrameForTime(&keyFrameTime, +// B_MEDIA_SEEK_CLOSEST_BACKWARD); +// printf("keyframe time for time: %lld -> %lld\n", time, keyFrameTime); +// } +} + +// destructor +MediaTrackVideoSupplier::~MediaTrackVideoSupplier() +{ +} + + +media_format +MediaTrackVideoSupplier::Format() const +{ + return fFormat; +} + + +status_t +MediaTrackVideoSupplier::ReadFrame(void* buffer, bigtime_t* performanceTime) +{ + if (!fVideoTrack) + return B_NO_INIT; + if (!buffer) + return B_BAD_VALUE; + + // read a frame + int64 frameCount = 1; + // TODO: how does this work for interlaced video (field count > 1)? + media_header mediaHeader; + status_t ret = fVideoTrack->ReadFrames(buffer, &frameCount, &mediaHeader); + + if (ret < B_OK) { + printf("MediaTrackVideoSupplier::ReadFrame() - " + "error while reading frame of track: %s\n", strerror(ret)); + } else { + fPerformanceTime = mediaHeader.start_time; + } + + if (performanceTime) + *performanceTime = fPerformanceTime; + +#if DEBUG_DECODED_FRAME +if (modifiers() & B_SHIFT_KEY) { +BFile fileStream("/boot/home/Desktop/decoded.png", B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); +BTranslatorRoster* roster = BTranslatorRoster::Default(); +BBitmap* bitmap = new BBitmap(Bounds(), 0, ColorSpace(), BytesPerRow()); +memcpy(bitmap->Bits(), buffer, bitmap->BitsLength()); +BBitmapStream bitmapStream(bitmap); +roster->Translate(&bitmapStream, NULL, NULL, &fileStream, B_PNG_FORMAT, 0); +bitmapStream.DetachBitmap(&bitmap); +delete bitmap; +} +#endif // DEBUG_DECODED_FRAME + + return ret; +} + + +status_t +MediaTrackVideoSupplier::SeekToTime(bigtime_t* performanceTime) +{ + if (!fVideoTrack) + return B_NO_INIT; + +bigtime_t _performanceTime = *performanceTime; + status_t ret = fVideoTrack->SeekToTime(performanceTime); + if (ret == B_OK) { +printf("seeked: %lld -> %lld\n", _performanceTime, *performanceTime); + fPerformanceTime = *performanceTime; + } + + return ret; +} + + +// #pragma mark - + + +BRect +MediaTrackVideoSupplier::Bounds() const +{ + return BRect(0, 0, fFormat.u.raw_video.display.line_width - 1, + fFormat.u.raw_video.display.line_count - 1); +} + + +color_space +MediaTrackVideoSupplier::ColorSpace() const +{ + return fFormat.u.raw_video.display.format; +} + + +uint32 +MediaTrackVideoSupplier::BytesPerRow() const +{ + return fFormat.u.raw_video.display.bytes_per_row; +} + + +// #pragma mark - + + +const char* +string_for_color_space(color_space format) +{ + const char* name = ""; + switch (format) { + case B_RGB32: + name = "B_RGB32"; + break; + case B_RGBA32: + name = "B_RGBA32"; + break; + case B_RGB32_BIG: + name = "B_RGB32_BIG"; + break; + case B_RGBA32_BIG: + name = "B_RGBA32_BIG"; + break; + case B_RGB24: + name = "B_RGB24"; + break; + case B_RGB24_BIG: + name = "B_RGB24_BIG"; + break; + case B_CMAP8: + name = "B_CMAP8"; + break; + case B_GRAY8: + name = "B_GRAY8"; + break; + case B_GRAY1: + name = "B_GRAY1"; + break; + + // YCbCr + case B_YCbCr422: + name = "B_YCbCr422"; + break; + case B_YCbCr411: + name = "B_YCbCr411"; + break; + case B_YCbCr444: + name = "B_YCbCr444"; + break; + case B_YCbCr420: + name = "B_YCbCr420"; + break; + + // YUV + case B_YUV422: + name = "B_YUV422"; + break; + case B_YUV411: + name = "B_YUV411"; + break; + case B_YUV444: + name = "B_YUV444"; + break; + case B_YUV420: + name = "B_YUV420"; + break; + + case B_YUV9: + name = "B_YUV9"; + break; + case B_YUV12: + name = "B_YUV12"; + break; + + default: + break; + } + return name; +} diff --git a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h new file mode 100644 index 0000000000..f9de398543 --- /dev/null +++ b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h @@ -0,0 +1,44 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef MEDIA_TRACK_VIDEO_SUPPLIER_H +#define MEDIA_TRACK_VIDEO_SUPPLIER_H + +#include "VideoSupplier.h" + +class BMediaTrack; + +class MediaTrackVideoSupplier : public VideoSupplier { + public: + MediaTrackVideoSupplier(BMediaTrack* track, + color_space preferredFormat); + virtual ~MediaTrackVideoSupplier(); + + virtual media_format Format() const; + virtual status_t ReadFrame(void* buffer, + bigtime_t* performanceTime); + virtual status_t SeekToTime(bigtime_t* performanceTime); + + virtual bigtime_t Position() const + { return fPerformanceTime; } + virtual bigtime_t Duration() const + { return fDuration; } + + virtual BRect Bounds() const; + virtual color_space ColorSpace() const; + virtual uint32 BytesPerRow() const; + + private: + BMediaTrack* fVideoTrack; + + media_format fFormat; + + bigtime_t fPerformanceTime; + bigtime_t fDuration; +}; + +#endif // MEDIA_TRACK_VIDEO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/VideoSupplier.cpp b/src/apps/mediaplayer/supplier/VideoSupplier.cpp new file mode 100644 index 0000000000..b02f51c030 --- /dev/null +++ b/src/apps/mediaplayer/supplier/VideoSupplier.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "VideoSupplier.h" + + +VideoSupplier::VideoSupplier() +{ +} + + +VideoSupplier::~VideoSupplier() +{ +} + diff --git a/src/apps/mediaplayer/supplier/VideoSupplier.h b/src/apps/mediaplayer/supplier/VideoSupplier.h new file mode 100644 index 0000000000..b24a8a7dbe --- /dev/null +++ b/src/apps/mediaplayer/supplier/VideoSupplier.h @@ -0,0 +1,27 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef VIDEO_SUPPLIER_H +#define VIDEO_SUPPLIER_H + +#include + +class VideoSupplier { + public: + VideoSupplier(); + virtual ~VideoSupplier(); + + virtual media_format Format() const = 0; + virtual status_t ReadFrame(void* buffer, + bigtime_t* performanceTime) = 0; + virtual status_t SeekToTime(bigtime_t* performanceTime) = 0; + + virtual bigtime_t Position() const = 0; + virtual bigtime_t Duration() const = 0; +}; + +#endif // VIDEO_SUPPLIER_H