From: APTX Date: Sat, 2 Mar 2013 12:01:56 +0000 (+0100) Subject: Continue porting X-Git-Url: https://gitweb.tyo.aptx.org/?a=commitdiff_plain;h=3aaaf1aee066059a7ff1ac735e725c0100cb97b5;p=aniplayer2.git Continue porting --- diff --git a/aniplayer2/aniplayer.cpp b/aniplayer2/aniplayer.cpp index cb750cc..396064f 100644 --- a/aniplayer2/aniplayer.cpp +++ b/aniplayer2/aniplayer.cpp @@ -1,7 +1,9 @@ #include "aniplayer.h" +#include +#include -AniPlayer::AniPlayer(QObject *parent) : QObject(parent), m_state(Stopped) +AniPlayer::AniPlayer(QObject *parent) : QObject(parent), m_state(NoFileLoaded) { } @@ -19,6 +21,31 @@ QString AniPlayer::currentFile() const return m_currentFile; } +qint64 AniPlayer::tickInterval() const +{ + return m_tickInterval; +} + +ChapterList AniPlayer::chapters() const +{ + return m_chapters; +} + +Chapter AniPlayer::chapter(int i) const +{ + return m_chapters[i]; +} + +StreamList AniPlayer::streams() const +{ + return m_streams; +} + +Stream *AniPlayer::stream(int i) const +{ + return m_streams[i]; +} + bool AniPlayer::open(const QString &file) { if (file == m_currentFile) @@ -31,14 +58,15 @@ bool AniPlayer::open(const QString &file) m_currentFile = file; emit currentFileChanged(m_currentFile); emit totalTimeChanged(totalTime()); + emit videoSizeChanged(videoSize()); return true; } void AniPlayer::play() { if (state() == Paused || state() == Stopped) - if (iplay()) - setState(Playing); + if (iplay()) + setState(Playing); } void AniPlayer::pause() @@ -59,10 +87,10 @@ void AniPlayer::stop() void AniPlayer::togglePause() { - if (state() == Paused) - play(); - else + if (state() == Playing) pause(); + else + play(); } bool AniPlayer::seek(qint64 position) @@ -75,9 +103,23 @@ void AniPlayer::skip(qint64 msec) seek(currentTime() + msec); } +bool AniPlayer::seek(const Chapter &chapter) +{ + return seek(chapter.time); +} + +bool AniPlayer::seekToChapter(int i) +{ + return seek(m_chapters[i].time); +} + bool AniPlayer::setVolume(double percent) { - return isetVolume(percent); + if(!isetVolume(percent)) + return false; + + emit volumeChanged(percent); + return true; } void AniPlayer::volumeUp(int by) @@ -98,14 +140,91 @@ void AniPlayer::changeVolume(int by) setVolume(newVolume); } +void AniPlayer::setMuted(bool muted) +{ + if (isetMuted(muted)) + emit mutedChanged(muted); +} + +void AniPlayer::setTickInterval(qint64 tickInterval) +{ + if (m_tickInterval != tickInterval) { + m_tickInterval = tickInterval; + emit tickIntervalChanged(tickInterval); + } +} + +bool AniPlayer::changeToStream(Stream *stream) +{ + return ichangeToStream(stream); +} + +bool AniPlayer::changeToStream(int i) +{ + return changeToStream(m_streams[i]); +} + +void AniPlayer::timerEvent(QTimerEvent *e) +{ + if (e->timerId() != tickTimer.timerId()) + return; + + const qint64 ct = currentTime(); + if (ct < lastTick + m_tickInterval) + return; + + lastTick = ct; + emit tick(ct); +} + +bool AniPlayer::seekInternal(qint64 position) +{ + qint64 ret = seek(position); + emit currentTimeChanged(position); + return ret; +} void AniPlayer::setState(AniPlayer::State newState) { if (m_state == newState) return; + if (newState == Playing) + { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + tickTimer.start(TICK_TIMER_INTERVAL, this); +#else + tickTimer.start(TICK_TIMER_INTERVAL, Qt::PreciseTimer, this); +#endif + lastTick = 0; + } + else + tickTimer.stop(); + State oldState = m_state; m_state = newState; emit stateChanged(newState); emit stateChanged(newState, oldState); } + +void AniPlayer::setChapters(const ChapterList &chapters) +{ + m_chapters = chapters; + emit chaptersChanged(chapters); +} + +void AniPlayer::setStreams(const StreamList &streams) +{ + qDeleteAll(m_streams); + m_streams = streams; + emit streamsChanged(streams); +} + + +void AniPlayer::fileFinished() +{ + ifileFinished(); + setState(Stopped); + emit playbackFinished(); +} + diff --git a/aniplayer2/aniplayer.h b/aniplayer2/aniplayer.h index 418c1bd..3ba45e2 100644 --- a/aniplayer2/aniplayer.h +++ b/aniplayer2/aniplayer.h @@ -6,8 +6,33 @@ #include #include #include +#include class VideoWidget; +class QTimerEvent; + +struct ANIPLAYER2SHARED_EXPORT Chapter +{ + QString name; + qint64 time; +}; +typedef QList ChapterList; + +enum StreamType +{ + VideoStream, + AudioStream, + SubtitleStream, + OtherStream +}; + +struct ANIPLAYER2SHARED_EXPORT Stream +{ + QString name; + StreamType type; + QString description; +}; +typedef QList StreamList; class ANIPLAYER2SHARED_EXPORT AniPlayer : public QObject { @@ -16,12 +41,17 @@ class ANIPLAYER2SHARED_EXPORT AniPlayer : public QObject Q_PROPERTY(qint64 currentTime READ currentTime WRITE seek STORED false) Q_PROPERTY(qint64 totalTime READ totalTime STORED false) Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged STORED false) + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(QSize videoSize READ videoSize) Q_PROPERTY(QString currentFile READ currentFile NOTIFY currentFileChanged) + Q_PROPERTY(qint64 tickInterval READ tickInterval WRITE setTickInterval NOTIFY tickIntervalChanged) + Q_PROPERTY(ChapterList chapters READ chapters NOTIFY chaptersChanged) + Q_PROPERTY(StreamList streams READ streams NOTIFY streamsChanged) public: enum State { + NoFileLoaded, Stopped, Playing, Paused, @@ -41,11 +71,20 @@ public: virtual qint64 totalTime() const = 0; virtual double volume() const = 0; + virtual bool isMuted() const = 0; virtual QSize videoSize() const = 0; QString currentFile() const; + qint64 tickInterval() const; + + ChapterList chapters() const; + Chapter chapter(int i) const; + + StreamList streams() const; + Stream *stream(int i) const; + public slots: bool open(const QString &file); void play(); @@ -56,34 +95,79 @@ public slots: bool seek(qint64 position); void skip(qint64 msec = 85000); + bool seek(const Chapter &chapter); + bool seekToChapter(int i); bool setVolume(double percent); void volumeUp(int by = 5); void volumeDown(int by = 5); void changeVolume(int by = 5); + void setMuted(bool muted); + + void setTickInterval(qint64 tickInterval); + + bool changeToStream(Stream *stream); + bool changeToStream(int i); signals: void stateChanged(AniPlayer::State newState); void stateChanged(AniPlayer::State newState, AniPlayer::State oldState); void volumeChanged(double newPercent); + void mutedChanged(bool muted); void currentFileChanged(QString file); + void videoSizeChanged(QSize newSize); + void currentTimeChanged(qint64 newCurrentTime); void totalTimeChanged(qint64 newTotalTime); + void playbackFinished(); + + void tick(qint64 current); + void tickIntervalChanged(qint64 arg); + void chaptersChanged(ChapterList chapters); + void streamsChanged(StreamList streams); + +protected slots: + void fileFinished(); + protected: + // Don't call i* methods directly. + // Call the versions without i. + + // Open file virtual bool iopen(const QString &file) = 0; + + // Playback virtual bool iplay() = 0; virtual bool ipause() = 0; virtual bool istop() = 0; + virtual void ifileFinished() {} + // Settings virtual bool iseek(qint64) = 0; virtual bool isetVolume(double) = 0; + virtual bool isetMuted(bool) = 0; + virtual bool ichangeToStream(Stream *stream) = 0; + + void timerEvent(QTimerEvent *); + + bool seekInternal(qint64 position); void setState(State newState); + void setChapters(const ChapterList &chapters); + void setStreams(const StreamList &streams); State m_state; QString m_currentFile; + + ChapterList m_chapters; + StreamList m_streams; + + qint64 m_tickInterval; + qint64 lastTick; + QBasicTimer tickTimer; + static const int TICK_TIMER_INTERVAL = 16; }; #endif // ANIPLAYER_H diff --git a/aniplayer2/aniplayer2.pri b/aniplayer2/aniplayer2.pri index 7757fbb..f7f0ae8 100644 --- a/aniplayer2/aniplayer2.pri +++ b/aniplayer2/aniplayer2.pri @@ -3,3 +3,5 @@ INCLUDEPATH += $$PWD DEPENDPATH += $$PWD LIBS += -laniplayer2 LIBS += -L$$PWD/../build + +include(../qtsingleapplication/qtsingleapplication.pri) diff --git a/aniplayer2/aniplayer2.pro b/aniplayer2/aniplayer2.pro index 05d613e..9caf300 100644 --- a/aniplayer2/aniplayer2.pro +++ b/aniplayer2/aniplayer2.pro @@ -9,17 +9,21 @@ TEMPLATE = lib DEFINES += ANIPLAYER2_LIBRARY -SOURCES += aniplayer.cpp - HEADERS += aniplayer2_global.h \ - aniplayer.h\ - videowidget.h - + constants.h \ + aniplayerapplication.h \ + aniplayer.h \ + videowidget.h \ + volumeslider.h -HEADERS += \ +SOURCES += aniplayer.cpp \ + videowidget.cpp \ + aniplayerapplication.cpp \ + volumeslider.cpp -SOURCES += \ - videowidget.cpp - include(../config.pri) +include(../qtsingleapplication/qtsingleapplication.pri) + +REV = $$system(git show-ref --head -s HEAD) +DEFINES += REVISION=\"$${REV}\" diff --git a/aniplayer2/aniplayerapplication.cpp b/aniplayer2/aniplayerapplication.cpp index 86b3c39..51cebe8 100644 --- a/aniplayer2/aniplayerapplication.cpp +++ b/aniplayer2/aniplayerapplication.cpp @@ -1,6 +1,46 @@ #include "aniplayerapplication.h" +#include +#include "constants.h" + AniPlayerApplication::AniPlayerApplication(int &argc, char **argv) : QtSingleApplication(argc, argv) { + QSettings::setDefaultFormat(QSettings::IniFormat); + setApplicationName(::applicationName); +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + setApplicationDisplayName(::applicationName); +#endif + setApplicationVersion(::applicationVersion); + setOrganizationName(::organizationName); +} + +void AniPlayerApplication::handleMessage(const QString &message) +{ + if (message.left(4) != "open") + return; + + QString file; + + int pos = -1; + if ((pos = message.indexOf(' ')) != -1) + file = message.mid(pos + 1); + + if (!file.isEmpty()) + emit openFileRequested(file); +} + +const char * const AniPlayerApplication::revision() +{ +#ifndef STRINGIFY +# define STRINGIFY_INTERNAL(x) #x +# define STRINGIFY(x) STRINGIFY_INTERNAL(x) +#endif + +#if defined(REVISION) + static const char *const revisionString = STRINGIFY(REVISION); +#else + static const char *const revisionString = "Unknown revision"; +#endif + return revisionString; } diff --git a/aniplayer2/aniplayerapplication.h b/aniplayer2/aniplayerapplication.h index 9ba08ae..6682941 100644 --- a/aniplayer2/aniplayerapplication.h +++ b/aniplayer2/aniplayerapplication.h @@ -4,16 +4,32 @@ #include "aniplayer2_global.h" #include -class AniPlayerApplication : public QtSingleApplication +class AniPlayer; + +#ifdef qApp +# undef qApp +#endif +#define qApp (AniPlayerApplication::instance()) + +class ANIPLAYER2SHARED_EXPORT AniPlayerApplication : public QtSingleApplication { Q_OBJECT public: explicit AniPlayerApplication(int &argc, char **argv); signals: + void openFileRequested(const QString &file); public slots: + void handleMessage(const QString &message); + +public: + inline static const AniPlayerApplication *instance() + { + return static_cast(QtSingleApplication::instance()); + } + static const char *const revision(); }; #endif // ANIPLAYERAPPLICATION_H diff --git a/aniplayer2/constants.h b/aniplayer2/constants.h new file mode 100644 index 0000000..fb69b10 --- /dev/null +++ b/aniplayer2/constants.h @@ -0,0 +1,8 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +static const char *applicationName = "AniPlayer"; +static const char *applicationVersion = "2.0.0B1"; +static const char *organizationName = "APTX"; + +#endif // CONSTANTS_H diff --git a/aniplayer2/volumeslider.cpp b/aniplayer2/volumeslider.cpp new file mode 100644 index 0000000..df19939 --- /dev/null +++ b/aniplayer2/volumeslider.cpp @@ -0,0 +1,77 @@ +#include "volumeslider.h" + +#include +#include +#include +#include + +VolumeSlider::VolumeSlider(QWidget *parent) : + QWidget(parent) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + muteButton = new QPushButton; + muteButton->setCheckable(true); + muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolume)); + + layout->addWidget(muteButton); + + slider = new QSlider; + slider->setOrientation(Qt::Horizontal); + slider->setMinimum(0); + slider->setMaximum(10000); + + layout->addWidget(slider); + + connect(muteButton, SIGNAL(clicked(bool)), this, SLOT(setMutedByUser(bool))); + connect(slider, SIGNAL(sliderMoved(int)), this, SLOT(setVolumeByUser(int))); +} + +double VolumeSlider::volume() const +{ + return double(slider->value()) / 10000; +} + +bool VolumeSlider::isMuted() const +{ + return muteButton->isChecked(); +} + + +void VolumeSlider::setVolume(double newVolume) +{ + if (volume() == newVolume) + return; + + slider->setValue(int(10000 * newVolume)); + emit volumeChanged(newVolume); +} + +void VolumeSlider::setMuted(bool mute) +{ + if (isMuted() == mute) + return; + + muteButton->setChecked(mute); + if (mute) + muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolumeMuted)); + else + muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolume)); + + emit mutedChanged(mute); +} + +void VolumeSlider::setMutedByUser(bool mute) +{ + if (mute) + muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolumeMuted)); + else + muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolume)); + emit mutedChangedByUser(mute); +} + +void VolumeSlider::setVolumeByUser(int newVolume) +{ + emit volumeChangedByUser(double(newVolume) / 10000); +} diff --git a/aniplayer2/volumeslider.h b/aniplayer2/volumeslider.h new file mode 100644 index 0000000..25e8fc5 --- /dev/null +++ b/aniplayer2/volumeslider.h @@ -0,0 +1,42 @@ +#ifndef VOLUMESLIDER_H +#define VOLUMESLIDER_H + +#include "aniplayer2_global.h" +#include + +class QSlider; +class QPushButton; + +class ANIPLAYER2SHARED_EXPORT VolumeSlider : public QWidget +{ + Q_OBJECT + Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged USER true) + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) + + +public: + explicit VolumeSlider(QWidget *parent = 0); + + double volume() const; + bool isMuted() const; + +signals: + void volumeChanged(double volume); + void volumeChangedByUser(double volume); + + void mutedChanged(bool muted); + void mutedChangedByUser(bool muted); + +public slots: + void setVolume(double newVolume); + void setVolumeByUser(int newVolume); + + void setMuted(bool mute); + void setMutedByUser(bool mute); + +private: + QSlider *slider; + QPushButton *muteButton; +}; + +#endif // VOLUMESLIDER_H diff --git a/aniplayer2_dshow/aniplayer2_dshow.pro b/aniplayer2_dshow/aniplayer2_dshow.pro index 7bef14d..43f1047 100644 --- a/aniplayer2_dshow/aniplayer2_dshow.pro +++ b/aniplayer2_dshow/aniplayer2_dshow.pro @@ -26,4 +26,4 @@ SOURCES += \ include(../config.pri) include(../aniplayer2/aniplayer2.pri) -LIBS += -lstrmiids -lole32 +LIBS += -lstrmiids -lole32 -lOleAut32 -lUser32 diff --git a/aniplayer2_dshow/aniplayerdshow.cpp b/aniplayer2_dshow/aniplayerdshow.cpp index 67e603e..5dc361c 100644 --- a/aniplayer2_dshow/aniplayerdshow.cpp +++ b/aniplayer2_dshow/aniplayerdshow.cpp @@ -8,19 +8,14 @@ AniPlayerDShow::AniPlayerDShow(QObject *parent) : AniPlayer(parent) { - HRESULT hr; - - hr = CoInitialize(NULL); - if (FAILED(hr)) - qDebug() << "Failed to initialize COM"; - m_videoWidget = new VideoWidgetDShow(this); d = new AniPlayerDShowInternal(this); } AniPlayerDShow::~AniPlayerDShow() { - CoUninitialize(); + delete d; + delete m_videoWidget; } QSize AniPlayerDShow::videoSize() const @@ -59,6 +54,11 @@ double AniPlayerDShow::volume() const return d->volume(); } +bool AniPlayerDShow::isMuted() const +{ + return d->isMuted(); +} + bool AniPlayerDShow::iopen(const QString &file) { if (!d->OpenFile(reinterpret_cast(file.utf16()))) @@ -80,7 +80,10 @@ bool AniPlayerDShow::ipause() bool AniPlayerDShow::istop() { - return d->stop(); + bool ret = d->stop(); + if (ret) + seekInternal(0); + return ret; } bool AniPlayerDShow::iseek(qint64 position) @@ -92,3 +95,18 @@ bool AniPlayerDShow::isetVolume(double volume) { return d->setVolume(volume); } + +bool AniPlayerDShow::isetMuted(bool muted) +{ + return d->setMuted(muted); +} + +bool AniPlayerDShow::ichangeToStream(Stream *stream) +{ + return d->changeToStream(static_cast(stream)); +} + +void AniPlayerDShow::ifileFinished() +{ + istop(); +} diff --git a/aniplayer2_dshow/aniplayerdshow.h b/aniplayer2_dshow/aniplayerdshow.h index 6d176d7..603dbab 100644 --- a/aniplayer2_dshow/aniplayerdshow.h +++ b/aniplayer2_dshow/aniplayerdshow.h @@ -10,6 +10,7 @@ struct AniPlayerDShowInternal; class ANIPLAYER2_DSHOWSHARED_EXPORT AniPlayerDShow : public AniPlayer { + friend struct AniPlayerDShowInternal; Q_OBJECT public: explicit AniPlayerDShow(QObject *parent = 0); @@ -25,6 +26,7 @@ public: qint64 totalTime() const; double volume() const; + bool isMuted() const; signals: @@ -36,6 +38,9 @@ protected: bool iseek(qint64 position); bool isetVolume(double volume); + bool isetMuted(bool muted); + bool ichangeToStream(Stream *stream); + void ifileFinished(); private: AniPlayerDShowInternal *d; diff --git a/aniplayer2_dshow/aniplayerdshowinternal.cpp b/aniplayer2_dshow/aniplayerdshowinternal.cpp index f181be6..5f88bcc 100644 --- a/aniplayer2_dshow/aniplayerdshowinternal.cpp +++ b/aniplayer2_dshow/aniplayerdshowinternal.cpp @@ -3,14 +3,24 @@ #include "videowidgetdshow.h" #include "aniplayerdshow.h" -#include +#include +#include +#include + #include +#include + HRESULT IsPinConnected(IPin *pPin, BOOL *pResult); HRESULT IsPinDirection(IPin *pPin, PIN_DIRECTION dir, BOOL *pResult); HRESULT AddFilterByCLSID(IGraphBuilder *pGraph, REFGUID clsid, IBaseFilter **ppF, LPCWSTR wszName); HRESULT FindConnectedPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin); +void _FreeMediaType(AM_MEDIA_TYPE& mt); +void _DeleteMediaType(AM_MEDIA_TYPE *pmt); + +DEFINE_GUID(MEDIATYPE_Subtitle, 0xE487EB08, 0x6B26, 0x4be9, 0x9D, 0xD3, 0x99, 0x34, 0x34, 0xD3, 0x13, 0xFD); + AniPlayerDShowInternal::AniPlayerDShowInternal(AniPlayerDShow *player_) : player(player_) { pGraph = 0; @@ -22,11 +32,22 @@ AniPlayerDShowInternal::AniPlayerDShowInternal(AniPlayerDShow *player_) : player pVideoDisplay = 0; pAudioControl = 0; + + mutedVolume = -1; + + HRESULT hr; + + hr = CoInitialize(NULL); + if (FAILED(hr)) + qDebug() << "Failed to initialize COM"; + } AniPlayerDShowInternal::~AniPlayerDShowInternal() { TearDownGraph(); + + CoUninitialize(); } bool AniPlayerDShowInternal::play() @@ -62,6 +83,9 @@ QSize AniPlayerDShowInternal::videoSize() const qint64 AniPlayerDShowInternal::totalTime() const { + if (!pSeeking) + return -1; + HRESULT hr = pSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) return -1; @@ -77,6 +101,9 @@ qint64 AniPlayerDShowInternal::totalTime() const qint64 AniPlayerDShowInternal::currentTime() const { + if (!pSeeking) + return -1; + HRESULT hr = pSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) return -1; @@ -92,6 +119,9 @@ qint64 AniPlayerDShowInternal::currentTime() const bool AniPlayerDShowInternal::seek(qint64 position) { + if (!pSeeking) + return false; + // Positions are in 100-nanoseconds, convert from miliseconds position *= 10000; @@ -111,6 +141,12 @@ bool AniPlayerDShowInternal::seek(qint64 position) double AniPlayerDShowInternal::volume() const { + if (!pAudioControl) + return -1; + + if (isMuted()) + return mutedVolume; + long lvolume; HRESULT hr = pAudioControl->get_Volume(&lvolume); @@ -125,6 +161,15 @@ double AniPlayerDShowInternal::volume() const bool AniPlayerDShowInternal::setVolume(double volume) { + if (!pAudioControl) + return false; + + if (isMuted()) + { + mutedVolume = volume; + return true; + } + volume = 1 - volume; long lvolume = -long(volume * 10000.0); @@ -135,8 +180,77 @@ bool AniPlayerDShowInternal::setVolume(double volume) return true; } +bool AniPlayerDShowInternal::isMuted() const +{ + return mutedVolume >= 0; +} + +bool AniPlayerDShowInternal::setMuted(bool muted) +{ + if (muted == isMuted()) + return true; + + if (muted) + { + double currentVolume = volume(); + + if (currentVolume < 0) + return false; + + if (!setVolume(0)) + return false; + + mutedVolume = currentVolume; + return true; + } + + double newVolume = mutedVolume; + mutedVolume = -1; + + if (setVolume(newVolume)) + return true; + + mutedVolume = newVolume; + return false; +} + +bool AniPlayerDShowInternal::changeToStream(StreamInternal *stream) +{ + Q_ASSERT(streamFilters.contains(stream->filter)); + + IAMStreamSelect *pStreamSelect = 0; + HRESULT hr = stream->filter->QueryInterface(IID_PPV_ARGS(&pStreamSelect)); + if (SUCCEEDED(hr)) + hr = pStreamSelect->Enable(stream->streamNo, AMSTREAMSELECTENABLE_ENABLE); + SafeRelease(&pStreamSelect); + + return SUCCEEDED(hr); +} + void AniPlayerDShowInternal::handleNotifications() { + if (!pEvent) + return; + + long evCode; + LONG_PTR param1, param2; + while (SUCCEEDED(pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0))) + { + pEvent->FreeEventParams(evCode, param1, param2); + switch (evCode) + { + case EC_COMPLETE: // Fall through. + case EC_USERABORT: // Fall through. + case EC_ERRORABORT: + player->fileFinished(); + return; + case EC_LENGTH_CHANGED: + emit player->totalTimeChanged(totalTime()); + break; + case EC_VIDEO_SIZE_CHANGED: + emit player->videoSizeChanged(videoSize()); + } + } } HRESULT AniPlayerDShowInternal::InitializeGraph() @@ -174,14 +288,14 @@ HRESULT AniPlayerDShowInternal::InitializeGraph() { goto done; } -/* + // Set up event notification. - hr = pEvent->SetNotifyWindow((OAHWND)m_hwnd, WM_GRAPH_EVENT, NULL); + hr = pEvent->SetNotifyWindow((OAHWND)hwnd(), WM_APP + 1, NULL); if (FAILED(hr)) { goto done; } -*/ + // m_state = STATE_STOPPED; done: @@ -190,13 +304,16 @@ done: void AniPlayerDShowInternal::TearDownGraph() { -/* + // Stop sending event messages if (pEvent) { pEvent->SetNotifyWindow((OAHWND)NULL, NULL, NULL); } -*/ + + for (auto i = streamFilters.constBegin(); i != streamFilters.constEnd(); ++i) + SafeRelease(&const_cast(*i)); + streamFilters.clear(); SafeRelease(&pGraph); SafeRelease(&pControl); SafeRelease(&pEvent); @@ -368,6 +485,138 @@ bool AniPlayerDShowInternal::OpenFile(PCWSTR pszFileName) // Try to render the streams. hr = RenderStreams(pSource); + { + IAMExtendedSeeking *pExtendedSeeking = 0; + + HRESULT hr = pSource->QueryInterface(IID_IAMExtendedSeeking, IID_PPV_ARGS_Helper(&pExtendedSeeking)); + + ChapterList chapters; + if (SUCCEEDED(hr)) + { + long count; + hr = pExtendedSeeking->get_MarkerCount(&count); + + // These "markers" actually start at 1 + for (long i = 1; i <= count; ++i) + { + BSTR name = 0; + double time; + Chapter chapter; + + hr = pExtendedSeeking->GetMarkerName(i, &name); + + if (SUCCEEDED(hr)) + chapter.name = QString::fromUtf16((ushort *)name); + + SysFreeString(name); + if (FAILED(hr)) + continue; + + // Time is returned in seconds + hr = pExtendedSeeking->GetMarkerTime(i, &time); + if (FAILED(hr)) + continue; + + chapter.time = qint64(time * 1000.0); + + chapters << chapter; + } + player->setChapters(chapters); + } + + SafeRelease(&pExtendedSeeking); + } + + { + StreamList streams; + IEnumFilters *enumFilters = 0; + + HRESULT hr = pGraph->EnumFilters(&enumFilters); + + if (SUCCEEDED(hr)) + { + IBaseFilter *filter = 0; + while (enumFilters->Next(1, &filter, NULL) == S_OK) + { + IAMStreamSelect *pStreamSelect = 0; + HRESULT hr = filter->QueryInterface(IID_PPV_ARGS(&pStreamSelect)); + + if (SUCCEEDED(hr)) + { + DWORD streamCount; + hr = pStreamSelect->Count(&streamCount); + + if (SUCCEEDED(hr)) + { + for (long streamNo = 0; streamNo < (long)streamCount; ++streamNo) + { + AM_MEDIA_TYPE *mediaType = 0; + DWORD pdwFlags; + LCID lcid; + DWORD pdwGroup; + WCHAR *wname = 0; + + QString name; + + hr = pStreamSelect->Info(streamNo, &mediaType, &pdwFlags, &lcid, &pdwGroup, &wname, NULL, NULL); + + if (mediaType) + _DeleteMediaType(mediaType); + + if (SUCCEEDED(hr)) + name = QString::fromUtf16((ushort *) wname); + + if (wname) + CoTaskMemFree(wname); + + if (FAILED(hr)) + continue; + + filter->AddRef(); + streamFilters << filter; + + StreamType type; + + if (IsEqualGUID(mediaType->majortype, MEDIATYPE_Video)) + type = VideoStream; + else if (IsEqualGUID(mediaType->majortype, MEDIATYPE_Audio)) + type = AudioStream; + else if (IsEqualGUID(mediaType->majortype, MEDIATYPE_Subtitle) + || IsEqualGUID(mediaType->majortype, MEDIATYPE_Text)) + type = SubtitleStream; + else + type = OtherStream; + + + + StreamInternal *stream = new StreamInternal; + stream->name = name; + stream->type = type; + if (lcid) + { + WCHAR wdesc[85]; + int rBytes = GetLocaleInfo(lcid, LOCALE_SNAME, wdesc, sizeof(wdesc)); + if (rBytes) + stream->description = QString::fromUtf16((ushort *)wdesc); + } + stream->streamNo = streamNo; + stream->filter = filter; + + streams << stream; +// qDebug() << "Stream" << streamNo << name << "Group:" << pdwGroup << "LCID:" << QString::number(lcid, 16) << stream->description; + } + } + } + SafeRelease(&pStreamSelect); + SafeRelease(&filter); + } + } + SafeRelease(&enumFilters); + + player->setStreams(streams); + } + + done: if (FAILED(hr)) { @@ -449,6 +698,29 @@ done: return hr; } +HWND AniPlayerDShowInternal::hwnd() const +{ + return (HWND) player->videoWidget()->winId(); +} + + +HRESULT AniPlayerDShowInternal::FinalizeGraph() +{ + if (pEVR == NULL) + { + return S_OK; + } + + BOOL bRemoved; + HRESULT hr = RemoveUnconnectedRenderer(pGraph, pEVR, &bRemoved); + if (bRemoved) + { + SafeRelease(&pEVR); + SafeRelease(&pVideoDisplay); + } + return hr; +} + HRESULT IsPinConnected(IPin *pPin, BOOL *pResult) { IPin *pTmp = NULL; @@ -554,25 +826,27 @@ HRESULT FindConnectedPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPi return hr; } -HWND AniPlayerDShowInternal::hwnd() const -{ - return (HWND) player->videoWidget()->winId(); -} - - -HRESULT AniPlayerDShowInternal::FinalizeGraph() +void _FreeMediaType(AM_MEDIA_TYPE& mt) { - if (pEVR == NULL) + if (mt.cbFormat != 0) { - return S_OK; + CoTaskMemFree((PVOID)mt.pbFormat); + mt.cbFormat = 0; + mt.pbFormat = NULL; + } + if (mt.pUnk != NULL) + { + // pUnk should not be used. + mt.pUnk->Release(); + mt.pUnk = NULL; } +} - BOOL bRemoved; - HRESULT hr = RemoveUnconnectedRenderer(pGraph, pEVR, &bRemoved); - if (bRemoved) +void _DeleteMediaType(AM_MEDIA_TYPE *pmt) +{ + if (pmt != NULL) { - SafeRelease(&pEVR); - SafeRelease(&pVideoDisplay); + _FreeMediaType(*pmt); + CoTaskMemFree(pmt); } - return hr; } diff --git a/aniplayer2_dshow/aniplayerdshowinternal.h b/aniplayer2_dshow/aniplayerdshowinternal.h index fe4274a..ea85231 100644 --- a/aniplayer2_dshow/aniplayerdshowinternal.h +++ b/aniplayer2_dshow/aniplayerdshowinternal.h @@ -5,16 +5,22 @@ #include -class AniPlayerDShow; +#include "aniplayerdshow.h" struct IBaseFilter; struct IGraphBuilder; struct IMediaControl; -struct IMediaEvent; +struct IMediaEventEx; struct IMediaSeeking; struct IMFVideoDisplayControl; struct IBasicAudio; +struct StreamInternal : public Stream +{ + int streamNo; + IBaseFilter *filter; +}; + struct AniPlayerDShowInternal { public: @@ -33,6 +39,10 @@ public: double volume() const; bool setVolume(double volume); + bool isMuted() const; + bool setMuted(bool muted); + + bool changeToStream(StreamInternal *stream); void handleNotifications(); @@ -51,7 +61,7 @@ public: IGraphBuilder *pGraph; IMediaControl *pControl; - IMediaEvent *pEvent; + IMediaEventEx *pEvent; IMediaSeeking *pSeeking; IBaseFilter *pEVR; @@ -66,6 +76,10 @@ public: HWND hwnd() const; AniPlayerDShow *player; + + QList streamFilters; + + double mutedVolume; }; template void SafeRelease(T **ppT) diff --git a/aniplayer2_dshow/videowidgetdshow.cpp b/aniplayer2_dshow/videowidgetdshow.cpp index 5fec830..bae8192 100644 --- a/aniplayer2_dshow/videowidgetdshow.cpp +++ b/aniplayer2_dshow/videowidgetdshow.cpp @@ -5,6 +5,8 @@ # include #endif +#include + VideoWidgetDShow::VideoWidgetDShow(AniPlayerDShow *player_, QWidget *parent) : VideoWidget(parent), player(player_) { @@ -29,13 +31,14 @@ void VideoWidgetDShow::resizeEvent(QResizeEvent *e) bool VideoWidgetDShow::nativeEvent(const QByteArray &eventType, void *message, long *result) { #ifdef Q_OS_WIN - if (eventType != "windows_generic_MSG") - return false; - + // This is the only type of event on windows. + Q_UNUSED(eventType) +// if (eventType != "windows_generic_MSG") +// return false; MSG *msg = static_cast(message); if (msg->message != WM_APP + 1) return false; -// player->ha + player->handleNotifications(); *result = 0; return true; #else diff --git a/player/aniplayer.exe.manifest b/player/aniplayer.exe.manifest new file mode 100644 index 0000000..bf74870 --- /dev/null +++ b/player/aniplayer.exe.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/player/aniplayer.qrc b/player/aniplayer.qrc new file mode 100644 index 0000000..4d8ed66 --- /dev/null +++ b/player/aniplayer.qrc @@ -0,0 +1,3 @@ + + + diff --git a/player/aniplayer.rc b/player/aniplayer.rc new file mode 100644 index 0000000..092cd15 --- /dev/null +++ b/player/aniplayer.rc @@ -0,0 +1,38 @@ +1 TYPELIB "aniplayer.rc" +IDI_ICON1 ICON DISCARDABLE "../resource/aniplayer-mikuru.ico" +1 24 "aniplayer.exe.manifest" +1 VERSIONINFO + FILEVERSION 2,0,0,0 + PRODUCTVERSION 2,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "APTX\0" + VALUE "FileDescription", "aniplayer\0" + VALUE "FileExtents", "xxx\0" + VALUE "FileOpenName", "Video Files (*.*)\0" + VALUE "FileVersion", "2, 0, 0, 0\0" + VALUE "InternalName", "aniplayer\0" + VALUE "LegalCopyright", "Copyright © 2009 APTX\0" + VALUE "MIMEType", "application/x-aniplayer\0" + VALUE "OriginalFilename", "aniplayer.exe\0" + VALUE "ProductName", "AniPlayer\0" + VALUE "ProductVersion", "2, 0, 0, 0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/player/main.cpp b/player/main.cpp index 0063be9..c227a57 100644 --- a/player/main.cpp +++ b/player/main.cpp @@ -1,11 +1,22 @@ #include "mainwindow.h" -#include +#include int main(int argc, char *argv[]) { - QApplication a(argc, argv); + AniPlayerApplication a(argc, argv); + + if (a.isRunning()) + { + if (a.arguments().count() > 1) + a.sendMessage("open " + a.arguments().at(1)); + + return 0; + } + MainWindow w; + QObject::connect(&a, SIGNAL(openFileRequested(QString)), &w, SLOT(play(QString))); + w.show(); - + return a.exec(); } diff --git a/player/mainwindow.cpp b/player/mainwindow.cpp index 42ee150..19ee8d6 100644 --- a/player/mainwindow.cpp +++ b/player/mainwindow.cpp @@ -3,8 +3,14 @@ #include #include -#include #include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +# include +#else +# include +#endif #ifdef Q_OS_WIN # include @@ -14,11 +20,16 @@ #include #include "menu.h" #include "seekslider.h" +#include +#include "versiondialog.h" + +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { +// setAttribute(Qt::WA_DeleteOnClose); dragged = mouseMoved = false; player = new AniPlayerDShow(this); @@ -63,7 +74,7 @@ MainWindow::MainWindow(QWidget *parent) : // connect(videoPlayer->mediaController(), SIGNAL(availableSubtitlesChanged()), this, SLOT(updateSubtitles())); // connect(m_actions["markWatched"], SIGNAL(triggered()), this, SLOT(markWatched())); // connect(m_actions["settings"], SIGNAL(triggered()), this, SLOT(anidbSettings())); -// connect(m_actions["about"], SIGNAL(triggered()), this, SLOT(about())); + connect(m_actions["about"], SIGNAL(triggered()), this, SLOT(about())); connect(m_actions["open"], SIGNAL(triggered()), this, SLOT(open())); connect(m_actions["play"], SIGNAL(triggered()), player, SLOT(play())); @@ -71,9 +82,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_actions["pause"], SIGNAL(triggered()), player, SLOT(pause())); connect(m_actions["stop"], SIGNAL(triggered()), player, SLOT(stop())); -// connect(m_actions["toggleStayOnTop"], SIGNAL(toggled(bool)), this, SLOT(toggleStayOnTop())); -// connect(m_actions["toggleFrameless"], SIGNAL(toggled(bool)), this, SLOT(toggleFrameless())); -// connect(m_actions["toggleOverlay"], SIGNAL(toggled(bool)), this, SLOT(toggleOverlay())); + connect(m_actions["toggleStayOnTop"], SIGNAL(toggled(bool)), this, SLOT(toggleStayOnTop())); + connect(m_actions["toggleFrameless"], SIGNAL(toggled(bool)), this, SLOT(toggleFrameless())); + connect(m_actions["toggleOverlay"], SIGNAL(toggled(bool)), this, SLOT(toggleOverlay())); connect(m_actions["volUp"], SIGNAL(triggered()), player, SLOT(volumeUp())); connect(m_actions["volDown"], SIGNAL(triggered()), player, SLOT(volumeDown())); @@ -81,21 +92,32 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_actions["opSkip"], SIGNAL(triggered()), this, SLOT(opSkip())); connect(m_actions["back1sec"], SIGNAL(triggered()), this, SLOT(skipback())); -// connect(videoPlayer->videoWidget(), SIGNAL(menuToggleRequested()), this, SLOT(toggleMenu())); - // connect(m_actions["next"], SIGNAL(triggered()), playlist, SLOT(next())); // connect(m_actions["previous"], SIGNAL(triggered()), playlist, SLOT(previous())); - connect(player, SIGNAL(totalTimeChanged(qint64)), menu->seekSlider(), SLOT(totalTimeChanged(qint64))); + connect(player, SIGNAL(totalTimeChanged(qint64)), menu, SLOT(totalTimeChanged(qint64))); connect(menu->seekSlider(), SIGNAL(seekRequested(qint64)), player, SLOT(seek(qint64))); + connect(player, SIGNAL(tick(qint64)), menu, SLOT(tick(qint64))); + connect(player, SIGNAL(currentFileChanged(QString)), this, SLOT(handleFileChange())); + connect(player, SIGNAL(chaptersChanged(ChapterList)), this, SLOT(chaptersChanged())); + connect(player, SIGNAL(streamsChanged(StreamList)), this, SLOT(streamsChanged())); + + connect(player, SIGNAL(volumeChanged(double)), menu->volumeSlider(), SLOT(setVolume(double))); + connect(menu->volumeSlider(), SIGNAL(volumeChangedByUser(double)), player, SLOT(setVolume(double))); + connect(player, SIGNAL(mutedChanged(bool)), menu->volumeSlider(), SLOT(setMuted(bool))); + connect(menu->volumeSlider(), SIGNAL(mutedChangedByUser(bool)), player, SLOT(setMuted(bool))); setCentralWidget(player->videoWidget()); - open("E:/Anime/Accel World OVA/Accel World OVA - 01 - OVA - [UTW][3e56ee18].mkv"); + handleStateChange(player->state(), player->state()); + //open("E:/Anime/Accel World OVA/Accel World OVA - 01 - OVA - [UTW][3e56ee18].mkv"); + updateWindowTitle(); + loadSettings(); } MainWindow::~MainWindow() { + saveSettings(); delete ui; } @@ -105,7 +127,11 @@ bool MainWindow::open() if (player->currentFile() == "") { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + dir = QDesktopServices::storageLocation(QDesktopServices::MoviesLocation); +#else dir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation); +#endif } else { @@ -180,7 +206,7 @@ void MainWindow::skipback() void MainWindow::opSkip() { - player->skip(/*m_opSkip*/85 * 1000); + player->skip(m_opSkip * 1000); } void MainWindow::toggleMenu() @@ -197,22 +223,25 @@ void MainWindow::resizeToVideo() void MainWindow::toggleStayOnTop() { updateWindowFlags(); + player->widgetChanged(); } void MainWindow::toggleFrameless() { updateWindowFlags(); + player->widgetChanged(); } void MainWindow::toggleOverlay() { updateWindowFlags(); + player->widgetChanged(); } void MainWindow::about() { -// VersionDialog dialog(this); - // dialog.exec(); + VersionDialog dialog(this); + dialog.exec(); } void MainWindow::handleStateChange(AniPlayer::State newState, AniPlayer::State oldState) @@ -221,6 +250,11 @@ void MainWindow::handleStateChange(AniPlayer::State newState, AniPlayer::State o switch(newState) { + case AniPlayer::NoFileLoaded: + m_actions["play"]->setDisabled(true); + m_actions["pause"]->setDisabled(true); + m_actions["stop"]->setDisabled(true); + break; case AniPlayer::Error: //menu->showMessage(playerlayer->errorString()); @@ -232,7 +266,6 @@ void MainWindow::handleStateChange(AniPlayer::State newState, AniPlayer::State o m_actions["play"]->setDisabled(false); m_actions["pause"]->setDisabled(true); m_actions["stop"]->setDisabled(true); - break; case AniPlayer::Playing: m_actions["play"]->setDisabled(true); @@ -249,8 +282,16 @@ void MainWindow::handleStateChange(AniPlayer::State newState, AniPlayer::State o } } +void MainWindow::handleFileChange() +{ + resize(player->videoSize()); +} + void MainWindow::mousePressEvent(QMouseEvent *event) { + if (player->videoWidget()->isFullScreen()) + return; + if (event->button() == Qt::LeftButton) { dragPosition = event->globalPos() - frameGeometry().topLeft(); @@ -356,17 +397,10 @@ void MainWindow::updateWindowTitle(const QFileInfo &file) void MainWindow::updateCursor() { -#ifdef Q_WS_X11 - if (isFullScreen() && menu->isHidden()) - setCursor(QCursor(Qt::BlankCursor)); + if (player->videoWidget()->isFullScreen() && menu->isHidden()) + player->videoWidget()->setCursor(QCursor(Qt::BlankCursor)); else - setCursor(QCursor(Qt::ArrowCursor)); -#else -// if (videoPlayer->videoWidget()->isFullScreen() && menu->isHidden()) -// videoPlayer->videoWidget()->setCursor(QCursor(Qt::BlankCursor)); -// else -// videoPlayer->videoWidget()->setCursor(QCursor(Qt::ArrowCursor)); -#endif + player->videoWidget()->setCursor(QCursor(Qt::ArrowCursor)); } void MainWindow::updateWindowFlags() @@ -403,3 +437,69 @@ void MainWindow::updateWindowFlags() show(); } + +void MainWindow::chaptersChanged() +{ +/* + foreach (const Chapter &c, player->chapters()) + { + qDebug() << "Chapter" << c.name << c.time; + } +*/ +} + +void MainWindow::streamsChanged() +{ +/* foreach (const Stream *c, player->streams()) + { + qDebug() << "Stream" << c->name << c->type << c->description; + } +*/ +} + +void MainWindow::saveSettings() +{ + QSettings settings; + + settings.beginGroup("settings"); + settings.setValue("currentFile", player->currentFile()); + settings.setValue("volume", player->volume()); + settings.setValue("muted", player->isMuted()); + settings.setValue("opSkip", m_opSkip); + settings.endGroup(); + settings.beginGroup("videoWindow"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("stayOnTop", m_actions["toggleStayOnTop"]->isChecked()); + settings.setValue("frameless", m_actions["toggleFrameless"]->isChecked()); + settings.endGroup(); + settings.beginGroup("menu"); + settings.setValue("geometry", menu->saveGeometry()); + settings.setValue("state", menu->saveState()); + settings.setValue("isVisible", menu->isVisible()); + settings.endGroup(); +} + +void MainWindow::loadSettings() +{ + QSettings settings; + settings.beginGroup("settings"); + open(settings.value("currentFile", "").toString()); + player->setVolume(settings.value("volume", qreal(1.0)).toDouble()); + player->setMuted(settings.value("muted", false).toBool()); + m_opSkip = settings.value("opSkip", 85).toInt(); + settings.endGroup(); + settings.beginGroup("videoWindow"); + restoreGeometry(settings.value("geometry", saveGeometry()).toByteArray()); + m_actions["toggleStayOnTop"]->setChecked(settings.value("stayOnTop", false).toBool()); + m_actions["toggleFrameless"]->setChecked(settings.value("frameless", false).toBool()); + settings.endGroup(); + settings.beginGroup("menu"); + menu->restoreState(settings.value("state", menu->saveState()).toByteArray()); + menu->restoreGeometry(settings.value("geometry", menu->saveGeometry()).toByteArray()); + menu->setVisible(settings.value("isVisible", true).toBool()); + settings.endGroup(); + settings.beginGroup("anidbudpapiclient"); +// m_automark = settings.value("automark", 0).toInt(); +// m_automarkPaths = settings.value("paths", QStringList()).toStringList(); + settings.endGroup(); +} diff --git a/player/mainwindow.h b/player/mainwindow.h index 8a4690a..acf2e5a 100644 --- a/player/mainwindow.h +++ b/player/mainwindow.h @@ -39,6 +39,10 @@ public slots: private slots: void handleStateChange(AniPlayer::State newState, AniPlayer::State oldState); + void handleFileChange(); + + void chaptersChanged(); + void streamsChanged(); protected: void mousePressEvent(QMouseEvent *event); @@ -58,12 +62,22 @@ private: void updateWindowFlags(); + void saveSettings(); + void loadSettings(); + QMap m_actions; Ui::MainWindow *ui; Menu *menu; AniPlayer *player; + int m_opSkip; + + int m_automark; + QStringList m_automarkPaths; + bool m_marked; + bool m_automarkable; + bool mouseMoved; QPoint dragPosition; bool dragged; diff --git a/player/menu.cpp b/player/menu.cpp new file mode 100644 index 0000000..3e0c1ce --- /dev/null +++ b/player/menu.cpp @@ -0,0 +1,173 @@ +#include "menu.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "seekslider.h" +#include + +Menu::Menu(QWidget *parent) + : QMainWindow(parent), dragged(false) +{ + setWindowFlags(Qt::Tool); + setAttribute(Qt::WA_QuitOnClose); + + controlBar = new QToolBar(tr("Control"), this); + seekBar = new QToolBar(tr("Seek"), this); + timeBar = new QToolBar(tr("Time"), this); + volumeBar = new QToolBar(tr("Volume"), this); + + statusBar(); + + controlBar->setObjectName("controlBar"); + volumeBar->setObjectName("volumeBar"); + timeBar->setObjectName("timeBar"); + seekBar->setObjectName("seekBar"); + + addToolBar(controlBar); + addToolBar(volumeBar); + addToolBarBreak(); + addToolBar(timeBar); + addToolBar(seekBar); + { + QWidget *seekBarContents = new QWidget(seekBar); + seekBarContents->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_seekSlider = new SeekSlider(seekBarContents); + + QHBoxLayout *layout = new QHBoxLayout(seekBarContents); +// layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_seekSlider); + seekBar->addWidget(seekBarContents); + } + { + QWidget *timeBarContents = new QWidget(timeBar); + timeBarContents->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + timeLabel = new QLabel("0:00:00 / 0:00:00 (0%)", timeBarContents); + + QHBoxLayout *layout = new QHBoxLayout(timeBarContents); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(timeLabel); + layout->setSizeConstraint(QLayout::SetMinimumSize); + timeBar->addWidget(timeBarContents); + } + { + QWidget *volumeBarContents = new QWidget(volumeBar); + m_volumeSlider = new VolumeSlider(this); + + QHBoxLayout *layout = new QHBoxLayout(volumeBarContents); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_volumeSlider); + volumeBar->addWidget(volumeBarContents); + } + + { + QWidget *controlBarContents = new QWidget(controlBar); + + QHBoxLayout *layout = new QHBoxLayout(controlBarContents); + layout->setSizeConstraint(QLayout::SetMinimumSize); + layout->setContentsMargins(0, 0, 0, 0); +// layout->addWidget(timeLabel, 1); +// layout->addWidget(m_volumeSlider, 2); + controlBar->addWidget(controlBarContents); + } + + setWindowTitle(tr("%1 Control Panel").arg(qApp->applicationName())); + + totalTime = " / " + QTime(0, 0, 0, 0).toString("h:mm:ss"); +} + +Menu::~Menu() +{ +} + +void Menu::addActions(QList actions) +{ + controlBar->addActions(actions); +} + +SeekSlider *Menu::seekSlider() const +{ + return m_seekSlider; +} + +VolumeSlider *Menu::volumeSlider() const +{ + return m_volumeSlider; +} + +void Menu::showMessage(const QString &message) +{ + statusBar()->showMessage(message); +} + +void Menu::tick(qint64 pos) +{ + m_seekSlider->tick(pos); + int sec = pos / 1000; + int min = sec / 60; + int hour = min / 60; + int msec = pos; + timeLabel->setText( + QString("%1 %2 (%3%)").arg( + QTime(hour, min % 60, sec % 60, msec % 1000).toString("h:mm:ss"), + totalTime) + .arg(int(pos * double(100) / length)) + ); +} + +void Menu::totalTimeChanged(qint64 time) +{ + m_seekSlider->totalTimeChanged(time); + length = time; + int sec = time / 1000; + int min = sec / 60; + int hour = min / 60; + int msec = time; + totalTime = " / " + QTime(hour, min % 60, sec % 60, msec % 1000).toString("h:mm:ss"); +} + +void Menu::moveEvent(QMoveEvent *event) +{ + QMainWindow::moveEvent(event); + emit positionChanged(); +} + +void Menu::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + dragPosition = event->globalPos() - frameGeometry().topLeft(); + dragged = true; + event->accept(); + } +} + +void Menu::mouseMoveEvent(QMouseEvent *event) +{ + if (!dragged) + return; + + if (event->buttons() & Qt::LeftButton) + { + move(event->globalPos() - dragPosition); + event->accept(); + } +} + +void Menu::mouseReleaseEvent(QMouseEvent * /*event*/) +{ + if (!dragged) + return; + dragged = false; +} + +void Menu::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + emit positionChanged(); +} diff --git a/player/menu.h b/player/menu.h new file mode 100644 index 0000000..98a8aaa --- /dev/null +++ b/player/menu.h @@ -0,0 +1,58 @@ +#ifndef MENU_H +#define MENU_H + +#include +#include + +class QToolBar; +class QMouseEvent; +class QLabel; +class SeekSlider; +class VolumeSlider; + +class Menu : public QMainWindow +{ + Q_OBJECT + +public: + Menu(QWidget *parent = 0); + ~Menu(); + + void addActions(QList actions); + + SeekSlider *seekSlider() const; + VolumeSlider *volumeSlider() const; + +public slots: + void showMessage(const QString &message); + void tick(qint64 msec); + void totalTimeChanged(qint64 time); + +signals: + void positionChanged(); + +protected: + virtual void moveEvent(QMoveEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseReleaseEvent(QMouseEvent *event); + virtual void resizeEvent(QResizeEvent *event); + +private: + SeekSlider *m_seekSlider; + VolumeSlider *m_volumeSlider; + + QToolBar *controlBar; + QToolBar *seekBar; + QToolBar *timeBar; + QToolBar *volumeBar; + + QLabel *timeLabel; + QString totalTime; + qint64 length; + + QPoint dragPosition; + bool dragged; +}; + +#endif // MENU_H diff --git a/player/player.pro b/player/player.pro index 16b2888..3660ff3 100644 --- a/player/player.pro +++ b/player/player.pro @@ -7,15 +7,16 @@ TEMPLATE = app TARGET = player DESTDIR = ../build +HEADERS += mainwindow.h \ + menu.h \ + seekslider.h \ + versiondialog.h SOURCES += main.cpp\ - mainwindow.cpp \ + mainwindow.cpp \ menu.cpp \ - seekslider.cpp - -HEADERS += mainwindow.h \ - menu.h \ - seekslider.h + seekslider.cpp \ + versiondialog.cpp FORMS += mainwindow.ui \ menu.ui @@ -23,3 +24,9 @@ FORMS += mainwindow.ui \ include(../config.pri) include(../aniplayer2/aniplayer2.pri) win32:include(../aniplayer2_dshow/aniplayer2_dshow.pri) + +win32 { + CONFIG -= embed_manifest_exe + RC_FILE += aniplayer.rc + LIBS += -luser32 +} diff --git a/player/seekslider.cpp b/player/seekslider.cpp new file mode 100644 index 0000000..b76bfb1 --- /dev/null +++ b/player/seekslider.cpp @@ -0,0 +1,239 @@ +#include "seekslider.h" + +#include +#include +#include +#include +#include + +#include "aniplayer.h" +#include + +SeekSlider::SeekSlider(QWidget *parent) : QWidget(parent) +{ + init(); +} + +void SeekSlider::init() +{ + ticking = false; + m_length = 1000; + m_seekPosition = 20; + markerWidth = 2; + drawMarkerShadow = false; + markerShadowEnabled = true; + maxPreviousPos = 3; + + setMouseTracking(true); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); +} + +SeekSlider::~SeekSlider() +{ + +} + +bool SeekSlider::isMarkerShadowEnabled() const +{ + return markerShadowEnabled; +} + +void SeekSlider::setMarkerShadowEnabled(bool enable) +{ + markerShadowEnabled = enable; +} + +QSize SeekSlider::sizeHint() const +{ + return QSize(); +} + +QSize SeekSlider::minimumSizeHint() const +{ + return QSize(100, 20); +} + +QSizePolicy SeekSlider::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +void SeekSlider::setSeekPosition(qint64 value) +{ + if (m_seekPosition == value) + return; + + m_seekPosition = value; + emit seekPositionChanged(value); + update(); +} + +void SeekSlider::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter p(this); + + seekerTop = 0;//height() / 3; + seekerLeft = 0; + seekerHeight = height() - 1;//height() / 3; + seekerWidth = width() - 1; + + const int markerWidth = 2; + const int markerPos = time2pos(m_seekPosition); + const QColor markerColor(255, 0, 0); + const QColor markerShadowColor(255, 0, 0, 100); + const QColor previousMarkerColor(255, 255, 255); + const QColor watchedPartColor(0, 0, 255, 150); + const QColor unwatchedPartColor(0, 255, 0, 100); + const QColor toolTipBackgroundColor(0, 0, 0, 150); + const QColor toolTipForegroundColor(255, 255, 255); + + + // border + p.drawRect(seekerLeft, seekerTop, seekerWidth, seekerHeight); + + // watched part + p.fillRect(seekerLeft + 1, seekerTop + 1, markerPos - seekerLeft - 1, seekerHeight - 1, watchedPartColor); + // unwatched part + p.fillRect(markerPos + markerWidth / 2, seekerTop + 1, seekerWidth - markerPos, seekerHeight - 1, unwatchedPartColor); + + int i = 1; + for (QQueue::const_iterator it = previuousPos.constBegin(); it != previuousPos.constEnd(); ++it) + { + int markerPos = seekerLeft + 1 + markerWidth / 2 + qRound(double(*it) / double(m_length) * double(seekerWidth - markerWidth / 2 - 1)); + QColor c = previousMarkerColor; + c.setAlpha(255 / previuousPos.count() * i); + p.fillRect(markerPos - markerWidth / 2, seekerTop + 1, markerWidth, seekerHeight - 1, c); + ++i; + } + + // marker bar + p.fillRect(markerPos - markerWidth / 2, seekerTop + 1, markerWidth, seekerHeight - 1, markerColor); + + // marker shadow (where the marker would move when mouse is clicked) + if (drawMarkerShadow && isEnabled()) + { + markerShadowPos = qBound(seekerLeft + 1 + markerWidth / 2, markerShadowPos, seekerLeft + seekerWidth - markerWidth / 2); + p.fillRect(markerShadowPos - markerWidth / 2, seekerTop + 1, markerWidth, seekerHeight - 1, markerShadowColor); + + QString time = QTime().addMSecs(pos2time(markerShadowPos)).toString("hh:mm:ss"); + QRect r = p.fontMetrics().boundingRect(time); + + int xdelta = markerShadowPos + markerWidth / 2 + 1; + if (xdelta + r.width() < width()) + r.translate(xdelta, r.height()); + else + r.translate(markerShadowPos - (markerWidth / 2 + 1) - r.width(), r.height()); + + p.fillRect(r, toolTipBackgroundColor); + p.setPen(QPen(toolTipForegroundColor)); + p.drawText(r, time); + } + +} + +void SeekSlider::mouseMoveEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + + markerShadowPos = event->pos().x(); + update(); +} + +void SeekSlider::enterEvent(QEvent *event) +{ + Q_UNUSED(event); + + drawMarkerShadow = true; + update(); +} + +void SeekSlider::leaveEvent(QEvent *event) +{ + Q_UNUSED(event); + + drawMarkerShadow = false; + update(); +} + +void SeekSlider::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) + return; + + seek(event->pos().x()); + + event->accept(); +} + +void SeekSlider::seek(qint64 msec) +{ + +} + +void SeekSlider::tick(qint64 msec) +{ + ticking = true; + setSeekPosition(msec); + ticking = false; +} + +void SeekSlider::totalTimeChanged(qint64 msec) +{ + ticking = true; + setLength(msec); + previuousPos.clear(); + ticking = false; +} + +void SeekSlider::seekableChanged(bool isSeekable) +{ + +} + +void SeekSlider::currentSourceChanged() +{ + //this releases the mouse and makes the seek slider stop seeking if the current source has changed + QMouseEvent event(QEvent::MouseButtonRelease, QPoint(), Qt::LeftButton, 0, 0); +// QApplication::sendEvent(this, &event); +} + +void SeekSlider::setLength(qint64 length) +{ + m_length = length; + update(); +} + +void SeekSlider::seek(int x) +{ + int newMarkerPos = qBound(seekerLeft + 1, x, seekerLeft + seekerWidth); + qint64 newSeekPos = pos2time(newMarkerPos); + + while (!previuousPos.isEmpty() && previuousPos.count() + 1 > maxPreviousPos) previuousPos.dequeue(); + previuousPos.enqueue(m_seekPosition); + + ticking = true; + setSeekPosition(newSeekPos); + emit seekRequested(newSeekPos); + ticking = false; + seek(newSeekPos); +} + +qint64 SeekSlider::pos2time(int pos) const +{ + const int halfMarkerWidth = markerWidth / 2; + return qint64(double(pos - (seekerLeft + 1 + halfMarkerWidth)) / double(seekerWidth - halfMarkerWidth - 1) * double(m_length)); +} + +int SeekSlider::time2pos(qint64 msec) const +{ + const int halfMarkerWidth = markerWidth / 2; + return seekerLeft + 1 + halfMarkerWidth + qRound(double(msec) / double(m_length) * double(seekerWidth - halfMarkerWidth - 1)); +} + + +qint64 SeekSlider::seekPosition() const +{ + return m_seekPosition; +} diff --git a/player/seekslider.h b/player/seekslider.h new file mode 100644 index 0000000..09dd954 --- /dev/null +++ b/player/seekslider.h @@ -0,0 +1,109 @@ +#ifndef SEEKSLIDER_H +#define SEEKSLIDER_H + +#include +#include + +class SeekSlider : public QWidget +{ + Q_OBJECT + Q_PROPERTY(qint64 seekPosition READ seekPosition WRITE setSeekPosition NOTIFY seekPositionChanged USER true) + + Q_DISABLE_COPY(SeekSlider) + + Q_PROPERTY(bool markerShadowEnabled READ isMarkerShadowEnabled WRITE setMarkerShadowEnabled) +/* + Q_PROPERTY(bool tracking READ hasTracking WRITE setTracking) + Q_PROPERTY(int pageStep READ pageStep WRITE setPageStep) + Q_PROPERTY(int singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) +*/ +public: + explicit SeekSlider(QWidget *parent = 0); + ~SeekSlider(); + + bool isMarkerShadowEnabled() const; + void setMarkerShadowEnabled(bool enable = true); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + QSizePolicy sizePolicy() const; +/* bool hasTracking() const; + void setTracking(bool tracking); + int pageStep() const; + void setPageStep(int milliseconds); + int singleStep() const; + void setSingleStep(int milliseconds); + Qt::Orientation orientation() const; +*/ + + + +/* + bool event(QEvent *event); +*/ + qint64 seekPosition() const; + + +public slots: +// void setOrientation(Qt::Orientation o); + + void setSeekPosition(qint64 position); + void tick(qint64 msec); + void totalTimeChanged(qint64 msec); + +signals: + void seekPositionChanged(qint64 arg); + void seekRequested(qint64 position); + +protected: + void paintEvent(QPaintEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void mouseReleaseEvent(QMouseEvent *ev); + +/* void mousePressEvent(QMouseEvent *ev); + + void initStyleOption(QStyleOptionSlider *option) const; +*/ +private slots: + void seek(qint64 msec); + void seekableChanged(bool isSeekable); + void currentSourceChanged(); + +private: + void init(); + void setLength(qint64 totalTimeChanged); + + void seek(int x); + + qint64 pos2time(int pos) const; + int time2pos(qint64 msec) const; + + bool ticking; + bool markerShadowEnabled; + + qint64 m_length; + bool m_tracking; + int m_pageStep; + int m_singleStep; + Qt::Orientation m_orientation; + + bool drawMarkerShadow; + int markerShadowPos; + + QQueue previuousPos; + int maxPreviousPos; + + // Seeker Geometry + int seekerTop; + int seekerLeft; + int seekerHeight; + int seekerWidth; + + int markerWidth; + qint64 m_seekPosition; +}; + +#endif // SEEKSLIDER_H diff --git a/player/versiondialog.cpp b/player/versiondialog.cpp new file mode 100644 index 0000000..b5b3f2f --- /dev/null +++ b/player/versiondialog.cpp @@ -0,0 +1,63 @@ +#include "versiondialog.h" + +#include +#include +#include +#include +#include + +#include "constants.h" +#include + +VersionDialog::VersionDialog(QWidget *parent) : QDialog(parent) +{ + setWindowTitle(tr("About %1").arg(qApp->applicationName())); + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QGridLayout *layout = new QGridLayout(this); + layout->setSizeConstraint(QLayout::SetFixedSize); + + QString revision; +#ifdef REVISION + revision = tr("from revision %1").arg(revisionString); +#endif + + const QString description = tr( + "

%1 %2 (%7 bit)

" + "

Built with\tQt %3
" + "Running with\tQt %4
" + "
" + "Built on " __DATE__ " at " __TIME__ " " + "%6" + "
" + "
" + "Application Icon (C) 2009 Watarase_Jun" + "
" + "
" + "Copyright (C) 2009 %5. All rights reserved.

" + "

The program is provided AS IS with NO WARRANTY OF ANY KIND, " + "INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A " + "PARTICULAR PURPOSE.

") + .arg(qApp->applicationName()) + .arg(qApp->applicationVersion()) + .arg(QLatin1String(QT_VERSION_STR)) + .arg(QLatin1String(qVersion())) + .arg(qApp->organizationName()) + .arg(revision) + .arg(QSysInfo::WordSize); + + QLabel *copyrightLabel = new QLabel(description); + copyrightLabel->setWordWrap(true); + copyrightLabel->setOpenExternalLinks(true); + copyrightLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + QPushButton *closeButton = buttonBox->button(QDialogButtonBox::Close); + + buttonBox->addButton(closeButton, QDialogButtonBox::ButtonRole(QDialogButtonBox::RejectRole | QDialogButtonBox::AcceptRole)); + connect(buttonBox , SIGNAL(rejected()), this, SLOT(reject())); + + layout->addWidget(copyrightLabel, 0, 1, 4, 4); + layout->addWidget(buttonBox, 4, 0, 1, 5); +} diff --git a/player/versiondialog.h b/player/versiondialog.h new file mode 100644 index 0000000..05cbd9e --- /dev/null +++ b/player/versiondialog.h @@ -0,0 +1,12 @@ +#ifndef VERSIONDIALOG_H +#define VERSIONDIALOG_H + +#include + +class VersionDialog : public QDialog +{ +public: + VersionDialog(QWidget *parent = 0); +}; + +#endif // VERSIONDIALOG_H diff --git a/resource/aniplayer-mikuru.ico b/resource/aniplayer-mikuru.ico new file mode 100644 index 0000000..2439682 Binary files /dev/null and b/resource/aniplayer-mikuru.ico differ diff --git a/resource/aniplayer.qrc b/resource/aniplayer.qrc new file mode 100644 index 0000000..1e4a5e3 --- /dev/null +++ b/resource/aniplayer.qrc @@ -0,0 +1,5 @@ + + + mikuru-icon-base.png + + diff --git a/resource/mikuru-icon-base.png b/resource/mikuru-icon-base.png new file mode 100644 index 0000000..7c362f8 Binary files /dev/null and b/resource/mikuru-icon-base.png differ diff --git a/resource/mikuru-icon-original.png b/resource/mikuru-icon-original.png new file mode 100644 index 0000000..7c0c662 Binary files /dev/null and b/resource/mikuru-icon-original.png differ