From b1d61ff5f8f19a50a56d6153a2f835fe24de81ec Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 24 Oct 2011 22:25:14 +0100 Subject: [PATCH] Initial 'proper' support for Video Playback The code to select media and then start/stop/pause playback during a workout has been implemented. This requires libvlc to be installed at compile time and will also require Videolan (and desired codecs) installed at runtime. Since the main headaches are likely to be related to deployment rather than coding this update has been pushed for deployment support to be enhanced and tested before closing the feature request on the bug tracker. --- src/MainWindow.h | 2 + src/TrainTool.cpp | 62 ++++++++++++++++++++++ src/TrainTool.h | 13 +++-- src/VideoWindow.cpp | 122 ++++++++++++++++++++++++++++++++++++++++---- src/VideoWindow.h | 22 ++++++++ 5 files changed, 209 insertions(+), 12 deletions(-) diff --git a/src/MainWindow.h b/src/MainWindow.h index 6ee77742c..afc88a02b 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -144,6 +144,7 @@ class MainWindow : public QMainWindow // realtime signals void notifyTelemetryUpdate(const RealtimeData &rtData) { telemetryUpdate(rtData); } void notifyErgFileSelected(ErgFile *x) { ergFileSelected(x); } + void notifyMediaSelected( QString x) { mediaSelected(x); } void notifySetNow(long now) { setNow(now); } void notifyNewLap() { emit newLap(); } void notifyStart() { emit start(); } @@ -180,6 +181,7 @@ class MainWindow : public QMainWindow // realtime void telemetryUpdate(RealtimeData rtData); void ergFileSelected(ErgFile *); + void mediaSelected(QString); void setNow(long); void newLap(); void start(); diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index 8ddc2fcba..39e3c3d92 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -35,6 +35,10 @@ #include "ANTlocalController.h" #include "NullController.h" +#ifdef GC_HAVE_VLC +// Media selection helper +#include "VideoWindow.h" +#endif TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), home(home), main(parent) { @@ -70,6 +74,19 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h serverTree->expandItem(allServers); #endif +#ifdef GC_HAVE_VLC + mediaTree = new QTreeWidget; + mediaTree->setFrameStyle(QFrame::NoFrame); + mediaTree->setColumnCount(1); + mediaTree->setSelectionMode(QAbstractItemView::SingleSelection); + mediaTree->header()->hide(); + mediaTree->setAlternatingRowColors (false); + mediaTree->setIndentation(5); + allMedia = new QTreeWidgetItem(mediaTree, HEAD_TYPE); + allMedia->setText(0, tr("Video / Media")); + mediaTree->expandItem(allMedia); +#endif + deviceTree = new QTreeWidget; deviceTree->setFrameStyle(QFrame::NoFrame); deviceTree->setSelectionMode(QAbstractItemView::MultiSelection); @@ -130,11 +147,17 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h trainSplitter->addWidget(deviceTree); //trainSplitter->addWidget(serverTree); trainSplitter->addWidget(workoutTree); +#ifdef GC_HAVE_VLC + trainSplitter->addWidget(mediaTree); +#endif // handle config changes //connect(serverTree,SIGNAL(itemSelectionChanged()), this, SLOT(serverTreeWidgetSelectionChanged())); connect(deviceTree,SIGNAL(itemSelectionChanged()), this, SLOT(deviceTreeWidgetSelectionChanged())); connect(workoutTree,SIGNAL(itemSelectionChanged()), this, SLOT(workoutTreeWidgetSelectionChanged())); +#ifdef GC_HAVE_VLC + connect(mediaTree,SIGNAL(itemSelectionChanged()), this, SLOT(mediaTreeWidgetSelectionChanged())); +#endif connect(main, SIGNAL(configChanged()), this, SLOT(configChanged())); // connect train tool buttons! @@ -243,6 +266,20 @@ TrainTool::configChanged() work->setText(0, name); } +#ifdef GC_HAVE_VLC + // MEDIA + QList media = allMedia->takeChildren(); + for (int i=0; isetText(0, video); + } +#endif + // Athlete FTP=285; // default to 285 if zones are not set int range = main->zones()->whichRange(QDate::currentDate()); @@ -396,6 +433,31 @@ TrainTool::listWorkoutFiles(const QDir &dir) const return dir.entryList(filters, QDir::Files, QDir::Name); } +void +TrainTool::mediaTreeWidgetSelectionChanged() +{ + assert(mediaTree->selectedItems().size() <= 1); + if (mediaTree->selectedItems().isEmpty()) + media = NULL; + else { + QTreeWidgetItem *which = mediaTree->selectedItems().first(); + if (which->type() != WORKOUT_TYPE) + media = NULL; + else + media = which; + } + + // which one is selected? + if (currentMedia() == NULL || currentMedia()->type() != WORKOUT_TYPE) { + main->notifyMediaSelected(""); + return; + } + + QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); + QString fileName = workoutDir.toString() + "/" + currentMedia()->text(0); // filename + main->notifyMediaSelected(fileName); +} + /*-------------------------------------------------------------------------------- * Was realtime window, now local and manages controller and chart updates etc *------------------------------------------------------------------------------*/ diff --git a/src/TrainTool.h b/src/TrainTool.h index 4f66fd5e6..2c802e034 100644 --- a/src/TrainTool.h +++ b/src/TrainTool.h @@ -80,7 +80,9 @@ class TrainTool : public GcWindow GoldenClient *streamController; // send out to const QTreeWidgetItem *currentWorkout() { return workout; } + const QTreeWidgetItem *currentMedia() { return media; } const QTreeWidgetItem *workoutItems() { return allWorkouts; } + const QTreeWidgetItem *mediaItems() { return allMedia; } const QTreeWidgetItem *currentServer() { return server; } const QTreeWidgetItem *serverItems() { return allServers; } @@ -118,6 +120,7 @@ class TrainTool : public GcWindow void serverTreeWidgetSelectionChanged(); void deviceTreeWidgetSelectionChanged(); void workoutTreeWidgetSelectionChanged(); + void mediaTreeWidgetSelectionChanged(); void configChanged(); public slots: @@ -146,16 +149,20 @@ class TrainTool : public GcWindow const QDir home; MainWindow *main; + QSplitter *trainSplitter; QTreeWidget *workoutTree; QTreeWidget *deviceTree; - QTreeWidgetItem *allWorkouts; - QTreeWidgetItem *workout; - QSplitter *trainSplitter; QTreeWidget *serverTree; + QTreeWidget *mediaTree; + QTreeWidgetItem *allServers; + QTreeWidgetItem *allMedia; QTreeWidgetItem *allDevices; QTreeWidgetItem *server; + QTreeWidgetItem *allWorkouts; + QTreeWidgetItem *workout; + QTreeWidgetItem *media; // those buttons QFrame *buttonPanel; diff --git a/src/VideoWindow.cpp b/src/VideoWindow.cpp index e32470ce6..2c03688e2 100644 --- a/src/VideoWindow.cpp +++ b/src/VideoWindow.cpp @@ -47,19 +47,16 @@ GcWindow(parent), home(home), main(parent) /* Create a new item */ // XXX need to add controls - not everyone is going to want to play a video from // my desktop!!! - m = libvlc_media_new_path(inst, "/home/markl/Videos/fightclub.mp4"); + + m = NULL; //vlc_exceptions(&exceptions); /* Create a media player playing environement */ mp = libvlc_media_player_new (inst); //vlc_exceptions(&exceptions); - /* set the media to playback */ - libvlc_media_player_set_media (mp, m); //vlc_exceptions(&exceptions); - /* No need to keep the media now */ - libvlc_media_release (m); /* This is a non working code that show how to hooks into a window, * if we have a window around */ @@ -78,8 +75,12 @@ GcWindow(parent), home(home), main(parent) libvlc_media_player_set_nsobject (mp, view); #endif - /* play the media_player */ - libvlc_media_player_play (mp); + connect(main, SIGNAL(stop()), this, SLOT(stopPlayback())); + connect(main, SIGNAL(start()), this, SLOT(startPlayback())); + connect(main, SIGNAL(pause()), this, SLOT(pausePlayback())); + connect(main, SIGNAL(unpause()), this, SLOT(resumePlayback())); + connect(main, SIGNAL(mediaSelected(QString)), this, SLOT(mediaSelected(QString))); + } VideoWindow::~VideoWindow() @@ -89,8 +90,12 @@ VideoWindow::~VideoWindow() x11Container->discardClient(); #endif - // stop playback & wipe player - libvlc_media_player_stop (mp); + stopPlayback(); + + /* No need to keep the media now */ + if (m) libvlc_media_release (m); + + /* nor the player */ libvlc_media_player_release (mp); // unload vlc @@ -101,3 +106,102 @@ void VideoWindow::resizeEvent(QResizeEvent * ) { // do nothing .. for now } + +void VideoWindow::startPlayback() +{ + + if (!m) return; // ignore if no media selected + + // stop playback & wipe player + libvlc_media_player_stop (mp); + + /* set the media to playback */ + libvlc_media_player_set_media (mp, m); + + /* play the media_player */ + libvlc_media_player_play (mp); +} +void VideoWindow::stopPlayback() +{ + if (!m) return; // ignore if no media selected + + // stop playback & wipe player + libvlc_media_player_stop (mp); +} + +void VideoWindow::pausePlayback() +{ + if (!m) return; // ignore if no media selected + + // stop playback & wipe player + libvlc_media_player_pause (mp); +} + +void VideoWindow::resumePlayback() +{ + if (!m) return; // ignore if no media selected + + // stop playback & wipe player + libvlc_media_player_pause (mp); +} + +void VideoWindow::mediaSelected(QString filename) +{ + // stop any current playback + stopPlayback(); + + // release whatever is already loaded + if (m) libvlc_media_release(m); + m = NULL; + + if (filename != "" && QFile(filename).exists()) { + + /* open media */ + m = libvlc_media_new_path(inst, filename.toLatin1()); + + /* set the media to playback */ + if (m) libvlc_media_player_set_media (mp, m); + } +} + +MediaHelper::MediaHelper() +{ + // config paramaters to libvlc + const char * const vlc_args[] = { + "-I", "dummy", /* Don't use any interface */ + "--ignore-config", /* Don't use VLC's config */ + "--extraintf=logger", //log anything + "--verbose=-1" // -1 = no output at all + }; + + /* Load the VLC engine */ + inst = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args); +} + +MediaHelper::~MediaHelper() +{ + // unload vlc + libvlc_release (inst); +} + +QStringList +MediaHelper::listMedia(QDir dir) +{ + QStringList returning; + + // whizz through every file in the directory + // and try and open it, if we succeed then huzzah + // otherwise ignore it + foreach(QString name, dir.entryList()) { + + libvlc_media_t *m = libvlc_media_new_path(inst, QString(dir.absolutePath() + "/" + name).toLatin1()); + + if (m) { + + libvlc_media_parse(m); + if (libvlc_media_get_duration(m) > 0) returning << name; + libvlc_media_release(m); + } + } + return returning; +} diff --git a/src/VideoWindow.h b/src/VideoWindow.h index 533ff0c1c..6b4549625 100644 --- a/src/VideoWindow.h +++ b/src/VideoWindow.h @@ -43,6 +43,22 @@ extern "C" { #include #endif +class MediaHelper +{ + public: + + MediaHelper(); + ~MediaHelper(); + + // get a list of supported media + // found in the supplied directory + QStringList listMedia(QDir directory); + + private: + + libvlc_instance_t * inst; +}; + class VideoWindow : public GcWindow { Q_OBJECT @@ -58,6 +74,12 @@ class VideoWindow : public GcWindow public slots: + void startPlayback(); + void stopPlayback(); + void pausePlayback(); + void resumePlayback(); + void mediaSelected(QString filename); + protected: void resizeEvent(QResizeEvent *);