Initial import from SVN
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WModelIndex>
|
||||
#include <Wt/WBreak> // TODO
|
||||
|
||||
#include "AudioDatabaseWidget.hpp"
|
||||
|
||||
#include "TableFilterWidget.hpp"
|
||||
#include "SearchFilterWidget.hpp"
|
||||
#include "TrackWidget.hpp"
|
||||
|
||||
AudioDatabaseWidget::AudioDatabaseWidget( Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_db("test.db"), // TODO
|
||||
_refreshingFilters(false)
|
||||
{
|
||||
std::size_t idFilter (0);
|
||||
{
|
||||
SearchFilterWidget* search = new SearchFilterWidget(this);
|
||||
_filters.push_back( search );
|
||||
search->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(_db, "genre", "name", this);
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(_db, "artist", "name", this);
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(_db, "release", "name", this);
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
|
||||
{
|
||||
TrackWidget* track = new TrackWidget(_db, this);
|
||||
_filters.push_back( track );
|
||||
track->trackSelected().connect(this, &AudioDatabaseWidget::handleTrackSelected);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::search(const std::string& text)
|
||||
{
|
||||
SearchFilterWidget* searchWidget ( dynamic_cast<SearchFilterWidget*>(_filters.front() ) );
|
||||
|
||||
searchWidget->setText(text);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::handleTrackSelected(boost::filesystem::path p)
|
||||
{
|
||||
_trackSelected.emit(p);
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::handleFilterUpdated(std::size_t idFilterUpdated)
|
||||
{
|
||||
// TODO disconnect from event!
|
||||
if (_refreshingFilters)
|
||||
return;
|
||||
|
||||
_refreshingFilters = true;
|
||||
|
||||
std::cout << "FILTER " << idFilterUpdated << " has been UPDATED!" << std::endl;
|
||||
|
||||
FilterWidget::Constraint currentConstraint;
|
||||
|
||||
currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id"));
|
||||
|
||||
for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter)
|
||||
{
|
||||
std::cout << "Processing Filter INDEX " << idFilter << std::endl;
|
||||
FilterWidget* filter = _filters.at(idFilter);
|
||||
|
||||
// Apply contraints created by previous filters
|
||||
if (idFilter > idFilterUpdated) {
|
||||
filter->refresh(currentConstraint);
|
||||
}
|
||||
|
||||
// Get constraints generated by this filter
|
||||
// (Note: adding accross successive calls)
|
||||
filter->getConstraint(currentConstraint);
|
||||
}
|
||||
|
||||
_refreshingFilters = false;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::selectNextTrack(void)
|
||||
{
|
||||
std::cout << "Wants to select next track!" << std::endl;
|
||||
|
||||
TrackWidget* trackWidget ( dynamic_cast<TrackWidget*>(_filters.back() ) );
|
||||
|
||||
trackWidget->selectNextTrack();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef AUDIO_DB_WIDGET_HPP
|
||||
#define AUDIO_DB_WIDGET_HPP
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WSignal>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "FilterWidget.hpp"
|
||||
|
||||
class AudioDatabaseWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
AudioDatabaseWidget( Wt::WContainerWidget *parent = 0);
|
||||
|
||||
void search(const std::string& text);
|
||||
|
||||
void selectNextTrack(void); // Will later emit the next selected track
|
||||
|
||||
// Signals
|
||||
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
|
||||
|
||||
private:
|
||||
|
||||
DatabaseHandler _db;
|
||||
|
||||
Wt::Signal< boost::filesystem::path > _trackSelected;
|
||||
|
||||
void handleTrackSelected(boost::filesystem::path p);
|
||||
void handleFilterUpdated(std::size_t idFilter);
|
||||
|
||||
std::vector<FilterWidget*> _filters; // Free Search, Genre, Artist, Release, etc.
|
||||
|
||||
bool _refreshingFilters;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WProgressBar>
|
||||
|
||||
#include "AudioMediaPlayerWidget.hpp"
|
||||
|
||||
|
||||
AudioMediaPlayerWidget::AudioMediaPlayerWidget( Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_mediaResource(nullptr)
|
||||
{
|
||||
_mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Audio, this );
|
||||
// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!"));
|
||||
// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay );
|
||||
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, "" );
|
||||
|
||||
_mediaPlayer->ended().connect(this, &AudioMediaPlayerWidget::handleTrackEnded);
|
||||
|
||||
{
|
||||
Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
|
||||
|
||||
_prevBtn = new Wt::WPushButton("<<", container );
|
||||
_playBtn = new Wt::WPushButton("Play", container );
|
||||
_pauseBtn = new Wt::WPushButton("Pause", container );
|
||||
_nextBtn = new Wt::WPushButton(">>", container );
|
||||
|
||||
_curTime = new Wt::WText(container);
|
||||
_timeSlider = new Wt::WSlider( container );
|
||||
_duration = new Wt::WText(container);
|
||||
|
||||
_volumeSlider = new Wt::WSlider( container );
|
||||
_volumeSlider->setRange(0,100);
|
||||
_volumeSlider->setValue(_mediaPlayer->volume() * 100);
|
||||
|
||||
_mediaPlayer->setControlsWidget( container );
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn);
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn);
|
||||
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime);
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration);
|
||||
|
||||
_mediaPlayer->timeUpdated().connect(this, &AudioMediaPlayerWidget::handleTimeUpdated);
|
||||
}
|
||||
|
||||
_timeSlider->valueChanged().connect(this, &AudioMediaPlayerWidget::handlePlayOffset);
|
||||
_timeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleSliderMoved);
|
||||
_timeSlider->setDisabled(true);
|
||||
|
||||
_volumeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleVolumeSliderMoved);
|
||||
|
||||
_nextBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayNext);
|
||||
_prevBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayPrev);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::loadPlayer(void)
|
||||
{
|
||||
_mediaPlayer->clearSources();
|
||||
|
||||
_mediaInternalLink.setResource( nullptr );
|
||||
if (_mediaResource)
|
||||
delete _mediaResource;
|
||||
|
||||
assert( _currentParameters );
|
||||
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
|
||||
_mediaInternalLink.setResource( _mediaResource );
|
||||
|
||||
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, _mediaInternalLink );
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
{
|
||||
_timeSlider->setDisabled(false);
|
||||
|
||||
_currentParameters = std::make_shared<Transcode::Parameters>( parameters );
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
|
||||
_timeSlider->setValue(0);
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayOffset(int offsetSecs)
|
||||
{
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
|
||||
std::cout << "Want to play at offset " << offsetSecs << std::endl;;
|
||||
|
||||
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayNext(void)
|
||||
{
|
||||
std::cout << "Want to play next!" << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayPrev(void)
|
||||
{
|
||||
std::cout << "Want to play prev!" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleTrackEnded(void)
|
||||
{
|
||||
std::cout << "Track playback ended!" << std::endl;
|
||||
_playbackEnded.emit();
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleValueChanged(double value)
|
||||
{
|
||||
std::cout << "Value changed!" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleSliderMoved(int value)
|
||||
{
|
||||
std::cout << "Slider moved to " << value << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleTimeUpdated(void)
|
||||
{
|
||||
std::cout << "Time updated to " << _mediaPlayer->currentTime() << std::endl;
|
||||
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters->getOffset().total_seconds()));
|
||||
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleVolumeSliderMoved(int value)
|
||||
{
|
||||
_mediaPlayer->setVolume( value / 100. );
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef __MEDIA_PLAYER_WIDGET_HPP
|
||||
#define __MEDIA_PLAYER_WIDGET_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Wt/WSlider>
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WLink>
|
||||
#include <Wt/WText>
|
||||
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
class AudioMediaPlayerWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
AudioMediaPlayerWidget( Wt::WContainerWidget *parent = 0);
|
||||
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
|
||||
// Signal slot Next
|
||||
Wt::Signal<void>& playbackEnded() {return _playbackEnded;}
|
||||
// Signal Slot Previous
|
||||
// Signal slot Ended
|
||||
|
||||
private:
|
||||
|
||||
void handlePlayOffset(int offsetSecs);
|
||||
void handlePlayNext(void);
|
||||
void handlePlayPrev(void);
|
||||
void handleTrackEnded(void);
|
||||
|
||||
void handleValueChanged(double);
|
||||
void handleTimeUpdated(void);
|
||||
void handleSliderMoved(int value);
|
||||
|
||||
void handleVolumeSliderMoved(int value);
|
||||
|
||||
void loadPlayer(void);
|
||||
|
||||
// Signals
|
||||
Wt::Signal<void> _playbackEnded;
|
||||
|
||||
// Core
|
||||
Wt::WMediaPlayer* _mediaPlayer;
|
||||
AvConvTranscodeStreamResource* _mediaResource;
|
||||
Wt::WLink _mediaInternalLink;
|
||||
|
||||
// Controls
|
||||
std::shared_ptr<Transcode::Parameters> _currentParameters;
|
||||
Wt::WPushButton* _playBtn;
|
||||
Wt::WPushButton* _pauseBtn;
|
||||
Wt::WPushButton* _nextBtn;
|
||||
Wt::WPushButton* _prevBtn;
|
||||
Wt::WSlider* _timeSlider;
|
||||
Wt::WSlider* _volumeSlider;
|
||||
Wt::WText* _curTime;
|
||||
Wt::WText* _duration;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <Wt/WBreak>
|
||||
|
||||
#include "AudioWidget.hpp"
|
||||
|
||||
#include "metadata/Extractor.hpp"
|
||||
|
||||
|
||||
AudioWidget::AudioWidget(Wt::WContainerWidget* parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_audioDbWidget(nullptr),
|
||||
_mediaPlayer(nullptr),
|
||||
_imgResource(nullptr),
|
||||
_img(nullptr)
|
||||
{
|
||||
_audioDbWidget = new AudioDatabaseWidget(this);
|
||||
|
||||
_audioDbWidget->trackSelected().connect(this, &AudioWidget::playTrack);
|
||||
|
||||
_mediaPlayer = new AudioMediaPlayerWidget(this);
|
||||
_mediaPlayer->playbackEnded().connect(this, &AudioWidget::handleTrackEnded);
|
||||
this->addWidget(new Wt::WBreak());
|
||||
|
||||
// Image
|
||||
_imgResource = new Wt::WMemoryResource(this);
|
||||
_imgLink.setResource( _imgResource);
|
||||
_img = new Wt::WImage(_imgLink, this);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioWidget::search(const std::string& searchText)
|
||||
{
|
||||
_audioDbWidget->search(searchText);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioWidget::playTrack(boost::filesystem::path p)
|
||||
{
|
||||
std::cout << "play track '" << p << "'" << std::endl;
|
||||
try {
|
||||
// TODO get user's encoding preference
|
||||
|
||||
Transcode::InputMediaFile inputFile(p);
|
||||
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA), 128000);
|
||||
|
||||
_mediaPlayer->load( parameters );
|
||||
|
||||
// Refresh cover
|
||||
{
|
||||
MetaData::Extractor parser;
|
||||
MetaData::GenericData cover;
|
||||
if (parser.parseCover( p, cover)) {
|
||||
std::cout << "Cover parsed!" << std::endl;
|
||||
std::cout << "mime type = " << cover.mimeType << ", size = " << cover.data.size();
|
||||
|
||||
_imgResource->setMimeType(cover.mimeType);
|
||||
_imgResource->setData(cover.data);
|
||||
|
||||
}
|
||||
else {
|
||||
std::cout << "No cover found!" << std::endl;
|
||||
_imgResource->setData( std::vector<unsigned char>());
|
||||
}
|
||||
|
||||
_imgResource->setChanged();
|
||||
}
|
||||
}
|
||||
catch( std::exception &e)
|
||||
{
|
||||
std::cerr <<"Caught exception while loading '" << p << "': " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioWidget::handleTrackEnded(void)
|
||||
{
|
||||
std::cout << "Track playback ended!" << std::endl;
|
||||
_audioDbWidget->selectNextTrack();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef AUDIO_WIDGET_HPP
|
||||
#define AUDIO_WIDGET_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <Wt/WLink>
|
||||
#include <Wt/WImage>
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WMemoryResource>
|
||||
|
||||
#include "audio/AudioMediaPlayerWidget.hpp"
|
||||
#include "audio/AudioDatabaseWidget.hpp"
|
||||
|
||||
class AudioWidget : public Wt::WContainerWidget
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
AudioWidget(Wt::WContainerWidget* parent = 0);
|
||||
|
||||
void search(const std::string& searchText);
|
||||
|
||||
private:
|
||||
|
||||
void playTrack(boost::filesystem::path p);
|
||||
|
||||
void handleTrackEnded(void);
|
||||
|
||||
AudioDatabaseWidget* _audioDbWidget;
|
||||
|
||||
AudioMediaPlayerWidget* _mediaPlayer;
|
||||
|
||||
// Image
|
||||
Wt::WMemoryResource *_imgResource;
|
||||
Wt::WLink _imgLink;
|
||||
Wt::WImage *_img;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef FILTER_WIDGET_HPP
|
||||
#define FILTER_WIDGET_HPP
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <Wt/WSignal>
|
||||
#include <Wt/WContainerWidget>
|
||||
|
||||
#include "database/SqlQuery.hpp"
|
||||
|
||||
class FilterWidget : public Wt::WContainerWidget {
|
||||
|
||||
public:
|
||||
|
||||
struct Constraint {
|
||||
WhereClause where; // WHERE SQL clause
|
||||
};
|
||||
|
||||
FilterWidget(Wt::WContainerWidget* parent = 0) : Wt::WContainerWidget(parent) {}
|
||||
virtual ~FilterWidget() {}
|
||||
|
||||
// Refresh filter Widget using constraints created by parent filters
|
||||
virtual void refresh(const Constraint& constraint) = 0;
|
||||
|
||||
// Update constraints for child filters
|
||||
virtual void getConstraint(Constraint& constraint) = 0;
|
||||
|
||||
// Emitted when a constraint has changed
|
||||
Wt::Signal<void>& update() { return _update; };
|
||||
|
||||
protected:
|
||||
|
||||
void emitUpdate() { _update.emit(); }
|
||||
|
||||
private:
|
||||
|
||||
Wt::Signal<void> _update;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <Wt/WLabel>
|
||||
|
||||
#include "SearchFilterWidget.hpp"
|
||||
|
||||
|
||||
SearchFilterWidget::SearchFilterWidget(Wt::WContainerWidget* parent)
|
||||
: FilterWidget( parent )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SearchFilterWidget::setText(const std::string& text)
|
||||
{
|
||||
if (text != _lastEmittedText) {
|
||||
_lastEmittedText = text;
|
||||
emitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Get constraints created by this filter
|
||||
void
|
||||
SearchFilterWidget::getConstraint(Constraint& constraint)
|
||||
{
|
||||
// No active search means no constaint!
|
||||
if (!_lastEmittedText.empty()) {
|
||||
const std::string bindText ("%%" + _lastEmittedText + "%%");
|
||||
constraint.where.And( WhereClause("(track.name like ? or release.name like ? or artist.name like ?)").bind(bindText).bind(bindText).bind(bindText));
|
||||
}
|
||||
|
||||
// else no constraint!
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef SEARCH_FILTER_WIDGET_PP
|
||||
#define SEARCH_FILTER_WIDGET_PP
|
||||
|
||||
#include <Wt/WLineEdit>
|
||||
|
||||
#include "FilterWidget.hpp"
|
||||
|
||||
class SearchFilterWidget : public FilterWidget {
|
||||
public:
|
||||
SearchFilterWidget(Wt::WContainerWidget* parent = 0);
|
||||
|
||||
void setText(const std::string& text);
|
||||
|
||||
// Set constraint on this filter
|
||||
void refresh(const Constraint& constraint) {}
|
||||
|
||||
// Get constraints created by this filter
|
||||
void getConstraint(Constraint& constraint);
|
||||
|
||||
private:
|
||||
|
||||
void handleKeyWentUp(void);
|
||||
|
||||
std::string _lastEmittedText;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "TableFilterWidget.hpp"
|
||||
|
||||
TableFilterWidget::TableFilterWidget(DatabaseHandler& db, std::string table, std::string field, Wt::WContainerWidget* parent)
|
||||
: FilterWidget( parent ),
|
||||
_db(db),
|
||||
_table(table),
|
||||
_field(field),
|
||||
_tableView(nullptr)
|
||||
{
|
||||
_queryModel.setQuery( _db.getSession().query< ResultType >("select " + _table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY from track,artist,release,genre,track_genre WHERE track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id GROUP BY " + _table + "." + _field + " UNION select '<All>',0,1 AS ORDERBY").orderBy("ORDERBY DESC," + _table + "." + _field));
|
||||
_queryModel.addColumn( _table + "." + _field, table);
|
||||
_queryModel.addColumn( "count(DISTINCT track.id)", "Tracks");
|
||||
|
||||
_tableView = new Wt::WTableView( this );
|
||||
_tableView->resize(250, 200); // TODO
|
||||
_tableView->setSelectionMode(Wt::ExtendedSelection);
|
||||
_tableView->setSortingEnabled(false);
|
||||
_tableView->setAlternatingRowColors(true);
|
||||
_tableView->setModel(&_queryModel);
|
||||
|
||||
_tableView->selectionChanged().connect(this, &TableFilterWidget::emitUpdate);
|
||||
_tableView->selectionChanged().connect(this, &TableFilterWidget::emitUpdate);
|
||||
|
||||
_queryModel.setBatchSize(100);
|
||||
}
|
||||
|
||||
// Set constraints on this filter
|
||||
void
|
||||
TableFilterWidget::refresh(const Constraint& constraint)
|
||||
{
|
||||
SqlQuery sqlQuery;
|
||||
|
||||
sqlQuery.select().And( _table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY");
|
||||
sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre")) ;
|
||||
sqlQuery.where().And(constraint.where); // Add constraint made by other filters
|
||||
sqlQuery.groupBy().And( _table + "." + _field); // Add constraint made by other filters
|
||||
|
||||
SqlQuery AllSqlQuery;
|
||||
AllSqlQuery.select().And( SelectStatement("'<All>',0,1 AS ORDERBY") );
|
||||
|
||||
std::cout << _table << ", generated query = '" << sqlQuery.get() + " UNION " + AllSqlQuery.get() << "'" << std::endl;
|
||||
|
||||
Wt::Dbo::Query<ResultType> query = _db.getSession().query<ResultType>( sqlQuery.get() + " UNION " + AllSqlQuery.get() );
|
||||
|
||||
query.orderBy("ORDERBY DESC," + _table + "." + _field);
|
||||
|
||||
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) {
|
||||
std::cout << "Binding value '" << bindArg << "'" << std::endl;
|
||||
query.bind(bindArg);
|
||||
}
|
||||
|
||||
_queryModel.setQuery( query, true /* Keep columns */);
|
||||
|
||||
std::cout << "Finish !" << std::endl;
|
||||
}
|
||||
|
||||
// Get constraint created by this filter
|
||||
void
|
||||
TableFilterWidget::getConstraint(Constraint& constraint)
|
||||
{
|
||||
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
|
||||
|
||||
// WHERE statement
|
||||
WhereClause clause;
|
||||
|
||||
BOOST_FOREACH(Wt::WModelIndex index, indexSet) {
|
||||
|
||||
if (!index.isValid())
|
||||
continue;
|
||||
|
||||
ResultType result = _queryModel.resultRow( index.row() );
|
||||
|
||||
// Get the track part
|
||||
std::string name( result.get<0>() );
|
||||
bool isAll( result.get<2>() ) ;
|
||||
|
||||
if (isAll) {
|
||||
// no constraint, just return
|
||||
return;
|
||||
}
|
||||
|
||||
clause.Or(_table + "." + _field + " = ?").bind(name);
|
||||
}
|
||||
|
||||
// Adding our WHERE clause
|
||||
constraint.where.And( clause );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef TABLE_VIEW_FILTER_WIDGET_HPP
|
||||
#define TABLE_VIEW_FILTER_WIDGET_HPP
|
||||
|
||||
|
||||
#include <Wt/Dbo/QueryModel>
|
||||
#include <Wt/WTableView>
|
||||
|
||||
#include "FilterWidget.hpp"
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
class TableFilterWidget : public FilterWidget
|
||||
{
|
||||
|
||||
public:
|
||||
TableFilterWidget(DatabaseHandler& db, std::string table, std::string field, Wt::WContainerWidget* parent = 0);
|
||||
|
||||
// Set constraints on this filter
|
||||
virtual void refresh(const Constraint& constraint);
|
||||
|
||||
// Get constraints created by this filter
|
||||
virtual void getConstraint(Constraint& constraint);
|
||||
|
||||
protected:
|
||||
|
||||
DatabaseHandler& _db;
|
||||
const std::string _table;
|
||||
const std::string _field;
|
||||
|
||||
typedef boost::tuple<std::string, int, int> ResultType;
|
||||
Wt::Dbo::QueryModel< ResultType > _queryModel;
|
||||
|
||||
Wt::WTableView* _tableView;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WItemDelegate>
|
||||
#include <Wt/WBreak>
|
||||
|
||||
#include "TrackWidget.hpp"
|
||||
|
||||
TrackWidget::TrackWidget( DatabaseHandler& db, Wt::WContainerWidget* parent)
|
||||
: FilterWidget( parent ),
|
||||
_db(db),
|
||||
_tableView(nullptr),
|
||||
_trackStats(nullptr)
|
||||
{
|
||||
|
||||
// ----- TRACK -----
|
||||
_queryModel.setQuery(_db.getSession().query<ResultType>("select track,release,artist from track,release,artist where track.release_id = release.id and track.artist_id = artist.id" ).orderBy("artist.name,release.name,track.disc_number,track.track_number"));
|
||||
_queryModel.addColumn( "artist.name", "Artist" );
|
||||
_queryModel.addColumn( "release.name", "Album" );
|
||||
_queryModel.addColumn( "track.disc_number", "Disc #" );
|
||||
_queryModel.addColumn( "track.track_number", "Track #" );
|
||||
_queryModel.addColumn( "track.name", "Track" );
|
||||
_queryModel.addColumn( "track.duration", "Duration" );
|
||||
_queryModel.addColumn( "track.creation_time", "Date" );
|
||||
_queryModel.addColumn( "track.genre_list", "Genres" );
|
||||
|
||||
_queryModel.setBatchSize(1000);
|
||||
|
||||
_tableView = new Wt::WTableView( this );
|
||||
_tableView->resize(Wt::WLength::Auto, 400);
|
||||
|
||||
_tableView->setSortingEnabled(true);
|
||||
_tableView->setSelectionMode(Wt::SingleSelection);
|
||||
_tableView->setAlternatingRowColors(true);
|
||||
_tableView->setModel(&_queryModel);
|
||||
|
||||
{
|
||||
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
|
||||
delegate->setTextFormat("yyyy");
|
||||
_tableView->setItemDelegateForColumn(6, delegate);
|
||||
}
|
||||
{
|
||||
// TODO better handle 1 hour+ files!
|
||||
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
|
||||
delegate->setTextFormat("mm:ss");
|
||||
_tableView->setItemDelegateForColumn(5, delegate);
|
||||
}
|
||||
|
||||
// TODO other event!
|
||||
_tableView->selectionChanged().connect(this, &TrackWidget::handleTrackSelected);
|
||||
|
||||
new Wt::WBreak(this);
|
||||
_trackStats = new Wt::WText(this);
|
||||
|
||||
updateStats();
|
||||
}
|
||||
|
||||
void
|
||||
TrackWidget::updateStats(void)
|
||||
{
|
||||
std::ostringstream oss; oss << "Files :" << _tableView->model()->rowCount();
|
||||
// TODO from UTF-8 ?
|
||||
_trackStats->setText(oss.str());
|
||||
}
|
||||
|
||||
// Set constraints created by parent filters
|
||||
void
|
||||
TrackWidget::refresh(const Constraint& constraint)
|
||||
{
|
||||
|
||||
SqlQuery sqlQuery;
|
||||
|
||||
sqlQuery.select().And( SelectStatement( "track,release,artist" ) );
|
||||
sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre"));
|
||||
sqlQuery.where().And(constraint.where);
|
||||
|
||||
std::cout << "TRACK REQ = '" << sqlQuery.get() << "'" << std::endl;
|
||||
|
||||
Wt::Dbo::Query<ResultType> query = _db.getSession().query<ResultType>( sqlQuery.get() );
|
||||
|
||||
query.groupBy("track").orderBy("artist.name,track.creation_time,release.name,track.disc_number,track.track_number");
|
||||
|
||||
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) {
|
||||
std::cout << "Binding value '" << bindArg << "'" << std::endl;
|
||||
query.bind(bindArg);
|
||||
}
|
||||
|
||||
_queryModel.setQuery( query, true );
|
||||
|
||||
updateStats();
|
||||
}
|
||||
|
||||
void
|
||||
TrackWidget::handleTrackSelected(void)
|
||||
{
|
||||
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
|
||||
if (!indexSet.empty()) {
|
||||
Wt::WModelIndex currentIndex( *indexSet.begin() );
|
||||
|
||||
// Check there are remainin tracks!
|
||||
if (currentIndex.isValid())
|
||||
{
|
||||
ResultType result = _queryModel.resultRow( currentIndex.row());
|
||||
|
||||
// Get the track part
|
||||
Wt::Dbo::ptr<Track> track ( result.get<0>() );
|
||||
|
||||
_trackSelected.emit( track->getPath() );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TrackWidget::selectNextTrack(void)
|
||||
{
|
||||
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
|
||||
if (!indexSet.empty()) {
|
||||
Wt::WModelIndex currentIndex( *indexSet.begin() );
|
||||
// Check there are remainin tracks!
|
||||
if (currentIndex.isValid() && _tableView->model()->rowCount() > currentIndex.row() + 1)
|
||||
{
|
||||
_tableView->select( _tableView->model()->index( currentIndex.row() + 1, currentIndex.column()));
|
||||
|
||||
ResultType result = _queryModel.resultRow( currentIndex.row() + 1 );
|
||||
|
||||
// Get the track part
|
||||
Wt::Dbo::ptr<Track> track ( result.get<0>() );
|
||||
|
||||
_trackSelected.emit( track->getPath() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef TRACK_WIDGET_HPP
|
||||
#define TRACK_WIDGET_HPP
|
||||
|
||||
#include <Wt/WTableView>
|
||||
#include <Wt/Dbo/QueryModel>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/AudioTypes.hpp"
|
||||
|
||||
#include "FilterWidget.hpp"
|
||||
|
||||
|
||||
class TrackWidget : public FilterWidget
|
||||
{
|
||||
public:
|
||||
|
||||
TrackWidget( DatabaseHandler& db, Wt::WContainerWidget* parent = 0);
|
||||
|
||||
// Set constraints created by parent filters
|
||||
void refresh(const Constraint& constraint);
|
||||
|
||||
// Create constraints for child filters (N/A)
|
||||
void getConstraint(Constraint& constraint) {}
|
||||
|
||||
void selectNextTrack(void); // Emit a trackSelected() if success
|
||||
|
||||
// Signals
|
||||
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
|
||||
|
||||
private:
|
||||
|
||||
void handleTrackSelected();
|
||||
|
||||
Wt::Signal< boost::filesystem::path > _trackSelected;
|
||||
|
||||
DatabaseHandler& _db;
|
||||
|
||||
typedef boost::tuple<Track::pointer, Release::pointer, Artist::pointer> ResultType;
|
||||
Wt::Dbo::QueryModel< ResultType > _queryModel;
|
||||
Wt::WTableView* _tableView;
|
||||
|
||||
void updateStats(void);
|
||||
|
||||
Wt::WText* _trackStats;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
#include <Wt/Http/Request>
|
||||
#include <Wt/Http/Response>
|
||||
#include "AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent)
|
||||
: Wt::WResource(parent),
|
||||
_parameters( parameters )
|
||||
{
|
||||
std::cout << "CONSTRUCTING RESOURCE" << std::endl;
|
||||
}
|
||||
|
||||
AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource()
|
||||
{
|
||||
std::cout << "DESTRUCTING RESOURCE" << std::endl;
|
||||
beingDeleted();
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response)
|
||||
{
|
||||
// see if this request is for a continuation:
|
||||
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder;
|
||||
if (continuation)
|
||||
transcoder = boost::any_cast<std::shared_ptr<Transcode::AvConvTranscoder> >(continuation->data());
|
||||
|
||||
if (!transcoder)
|
||||
{
|
||||
std::cout << "Launching transcoder" << std::endl;
|
||||
transcoder = std::make_shared<Transcode::AvConvTranscoder>( _parameters);
|
||||
|
||||
response.setMimeType(_parameters.getOutputFormat().getMimeType());
|
||||
}
|
||||
|
||||
Transcode::AvConvTranscoder::data_type& data = transcoder->getOutputData();
|
||||
|
||||
while (!transcoder->isComplete() && data.size() < _bufferSize)
|
||||
transcoder->process();
|
||||
|
||||
// Give the client all the output data
|
||||
Transcode::AvConvTranscoder::data_type::const_iterator it = data.begin();
|
||||
bool copySuccess = true;
|
||||
std::size_t copiedSize = 0;
|
||||
for (Transcode::AvConvTranscoder::data_type::const_iterator it = data.begin(); it != data.end(); ++it)
|
||||
{
|
||||
if (!response.out().put(*it)) {
|
||||
copySuccess = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
copiedSize++;
|
||||
}
|
||||
|
||||
std::cout << "Wrote " << copiedSize << " bytes" << std::endl;
|
||||
|
||||
if (!copySuccess)
|
||||
std::cerr << "** Write failed!" << std::endl;
|
||||
|
||||
// Consume copied bytes
|
||||
data.erase(data.begin(), data.begin() + copiedSize);
|
||||
|
||||
if (copySuccess && !transcoder->isComplete()) {
|
||||
continuation = response.createContinuation();
|
||||
continuation->setData(transcoder);
|
||||
std::cout << "Continuation set!" << std::endl;
|
||||
}
|
||||
else
|
||||
std::cout << "No more data!" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef AVCONV_TRANSCODE_STREAM_RESOURCE_HPP
|
||||
#define AVCONV_TRANSCODE_STREAM_RESOURCE_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Wt/WResource>
|
||||
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
|
||||
class AvConvTranscodeStreamResource : public Wt::WResource
|
||||
{
|
||||
public:
|
||||
AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent = 0);
|
||||
~AvConvTranscodeStreamResource();
|
||||
|
||||
void handleRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response);
|
||||
|
||||
private:
|
||||
Transcode::Parameters _parameters;
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> _transcoder;
|
||||
|
||||
std::streamsize _beyondLastByte;
|
||||
static const std::size_t _bufferSize = 8192*16;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WText>
|
||||
#include <Wt/WPushButton>
|
||||
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WFileResource>
|
||||
#include "transcode/Parameters.hpp"
|
||||
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
#include "VideoDatabaseWidget.hpp"
|
||||
|
||||
|
||||
VideoDatabaseWidget::VideoDatabaseWidget( Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_db("test.db") // TODO
|
||||
{
|
||||
_table = new Wt::WTable( this );
|
||||
_table->setHeaderCount(1);
|
||||
|
||||
Wt::Dbo::Transaction transaction ( _db.getSession() );
|
||||
|
||||
updateView( Path::pointer() );
|
||||
|
||||
_table->addStyleClass("table form-inline");
|
||||
|
||||
_table->toggleStyleClass("table-hover", true);
|
||||
_table->toggleStyleClass("table-striped", true);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::addHeader(void)
|
||||
{
|
||||
_table->elementAt(0, 0)->addWidget(new Wt::WText("Name"));
|
||||
_table->elementAt(0, 1)->addWidget(new Wt::WText("Duration"));
|
||||
_table->elementAt(0, 2)->addWidget(new Wt::WText("Action"));
|
||||
}
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::addDirectory(const std::string& name, const boost::filesystem::path& path)
|
||||
{
|
||||
|
||||
int row = _table->rowCount();
|
||||
|
||||
std::cout << "Adding DIR at row " << row << ", path = '" << path << "'" << std::endl;
|
||||
|
||||
new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0));
|
||||
new Wt::WText(Wt::WString::fromUTF8( " " ), _table->elementAt(row, 1));
|
||||
|
||||
Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Open"), _table->elementAt(row, 2));
|
||||
btn->clicked().connect( boost::bind(&VideoDatabaseWidget::handleOpenDirectory, this, path) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)
|
||||
{
|
||||
|
||||
int row = _table->rowCount();
|
||||
|
||||
std::cout << "Adding VIDEO at row " << row << ", path = '" << path << "'" << std::endl;
|
||||
|
||||
new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0));
|
||||
new Wt::WText(Wt::WString::fromUTF8( boost::posix_time::to_simple_string( duration )), _table->elementAt(row, 1));
|
||||
|
||||
Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Play"), _table->elementAt(row, 2));
|
||||
btn->clicked().connect( boost::bind(&VideoDatabaseWidget::handlePlayVideo, this, path) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::updateView(Path::pointer directory)
|
||||
{
|
||||
// Clear table from row 1 to end
|
||||
// while(_table->rowCount() > 2)
|
||||
// _table->deleteRow( _table->rowCount() - 1);
|
||||
|
||||
_table->clear();
|
||||
|
||||
addHeader();
|
||||
|
||||
std::vector< Path::pointer > pathes;
|
||||
if(directory)
|
||||
{
|
||||
pathes = directory->getChilds();
|
||||
// Add parent directory
|
||||
addDirectory("<Parent>", directory->getParent() ? directory->getParent() ->getPath() : boost::filesystem::path());
|
||||
}
|
||||
else
|
||||
pathes = Path::getRoots(_db.getSession() );
|
||||
|
||||
// Add childs
|
||||
BOOST_FOREACH(Path::pointer path, pathes)
|
||||
{
|
||||
std::cout << "Adding path " << path->getPath() << std::endl;
|
||||
if (path->isDirectory())
|
||||
addDirectory( path->getFileName(), path->getPath());
|
||||
else if (path->getVideo())
|
||||
addVideo( path->getFileName(), path->getVideo()->getDuration(), path->getPath());
|
||||
else
|
||||
std::cerr << "Bad type??" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::handleOpenDirectory(boost::filesystem::path path)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction ( _db.getSession() );
|
||||
|
||||
Path::pointer directory = Path::getByPath(_db.getSession(), path);
|
||||
updateView(directory);
|
||||
}
|
||||
|
||||
void
|
||||
VideoDatabaseWidget::handlePlayVideo(const boost::filesystem::path& videoFilePath)
|
||||
{
|
||||
std::cout << "Wants to play video " << videoFilePath << std::endl;
|
||||
|
||||
Wt::Dbo::Transaction transaction ( _db.getSession() );
|
||||
|
||||
Path::pointer path = Path::getByPath(_db.getSession(), videoFilePath);
|
||||
Video::pointer video;
|
||||
if (path)
|
||||
video = path->getVideo();
|
||||
|
||||
if (!video)
|
||||
{
|
||||
std::cerr << "Cannot find video for this path '" << videoFilePath << "'" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit signal
|
||||
playVideo().emit(videoFilePath);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef VIDEO_DB_WIDGET_HPP
|
||||
#define VIDEO_DB_WIDGET_HPP
|
||||
|
||||
#include <Wt/WTable>
|
||||
#include <Wt/WContainerWidget>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/FileTypes.hpp"
|
||||
|
||||
class VideoDatabaseWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
VideoDatabaseWidget( Wt::WContainerWidget *parent = 0);
|
||||
|
||||
// Signals
|
||||
Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }
|
||||
|
||||
private:
|
||||
|
||||
|
||||
void updateView(Path::pointer directory);
|
||||
void handleOpenDirectory(boost::filesystem::path directory);
|
||||
void handlePlayVideo(const boost::filesystem::path& path);
|
||||
|
||||
void addHeader(void);
|
||||
void addDirectory(const std::string& name, const boost::filesystem::path& path);
|
||||
void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);
|
||||
|
||||
Wt::Signal< boost::filesystem::path > _playVideo;
|
||||
|
||||
DatabaseHandler _db;
|
||||
|
||||
Wt::WTable* _table;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WMediaPlayer>
|
||||
|
||||
#include "VideoParametersDialog.hpp"
|
||||
|
||||
#include "VideoMediaPlayerWidget.hpp"
|
||||
|
||||
|
||||
Wt::WMediaPlayer::Encoding
|
||||
convert(Transcode::Format format)
|
||||
{
|
||||
switch( format.getEncoding() )
|
||||
{
|
||||
case Transcode::Format::OGA: return Wt::WMediaPlayer::OGA;
|
||||
case Transcode::Format::OGV: return Wt::WMediaPlayer::OGV;
|
||||
case Transcode::Format::MP3: return Wt::WMediaPlayer::MP3;
|
||||
case Transcode::Format::WEBMA: return Wt::WMediaPlayer::WEBMA;
|
||||
case Transcode::Format::WEBMV: return Wt::WMediaPlayer::WEBMV;
|
||||
case Transcode::Format::FLV: return Wt::WMediaPlayer::FLV;
|
||||
case Transcode::Format::M4A: return Wt::WMediaPlayer::M4A;
|
||||
case Transcode::Format::M4V: return Wt::WMediaPlayer::M4V;
|
||||
}
|
||||
assert(0);
|
||||
}
|
||||
|
||||
|
||||
VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_mediaResource(nullptr),
|
||||
_currentParameters(parameters),
|
||||
_dialog(nullptr)
|
||||
{
|
||||
_mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Video, this );
|
||||
// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!"));
|
||||
// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay );
|
||||
// _mediaPlayer->addSource( Wt::WMediaPlayer::WEBMV, "" );
|
||||
|
||||
{
|
||||
Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
|
||||
|
||||
_playBtn = new Wt::WPushButton("Play", container );
|
||||
_pauseBtn = new Wt::WPushButton("Pause", container );
|
||||
|
||||
_curTime = new Wt::WText(container);
|
||||
_timeSlider = new Wt::WSlider( container );
|
||||
_duration = new Wt::WText(container);
|
||||
|
||||
_volumeSlider = new Wt::WSlider( container );
|
||||
_volumeSlider->setRange(0,100);
|
||||
_volumeSlider->setValue(_mediaPlayer->volume() * 100);
|
||||
|
||||
_mediaPlayer->setControlsWidget( container );
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn);
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn);
|
||||
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime);
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration);
|
||||
|
||||
_mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated);
|
||||
|
||||
Wt::WPushButton* fullScreenButton = new Wt::WPushButton("Fullscreen", container);
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::FullScreen, fullScreenButton);
|
||||
|
||||
Wt::WPushButton* restoreScreenButton = new Wt::WPushButton("Restore screen", container);
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::RestoreScreen, restoreScreenButton);
|
||||
|
||||
_mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated);
|
||||
|
||||
_timeSlider->valueChanged().connect(this, &VideoMediaPlayerWidget::handlePlayOffset);
|
||||
_timeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleSliderMoved);
|
||||
|
||||
_volumeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleVolumeSliderMoved);
|
||||
}
|
||||
|
||||
|
||||
Wt::WPushButton* closeButton = new Wt::WPushButton("Close", this);
|
||||
closeButton->clicked().connect( this, &VideoMediaPlayerWidget::handleClose );
|
||||
|
||||
Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this);
|
||||
parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit );
|
||||
|
||||
load(parameters);
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
{
|
||||
_mediaPlayer->clearSources();
|
||||
|
||||
_currentParameters = parameters;
|
||||
|
||||
_mediaInternalLink.setResource( nullptr );
|
||||
if (_mediaResource)
|
||||
delete _mediaResource;
|
||||
|
||||
_mediaResource = new AvConvTranscodeStreamResource( parameters, this );
|
||||
_mediaInternalLink.setResource( _mediaResource );
|
||||
|
||||
_mediaPlayer->addSource( convert(parameters.getOutputFormat()), _mediaInternalLink );
|
||||
|
||||
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
|
||||
_timeSlider->setValue( parameters.getOffset().total_seconds() );
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
|
||||
{
|
||||
std::cout << "Want to play at offset " << offsetSecs << std::endl;;
|
||||
_currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
load( _currentParameters );
|
||||
|
||||
_timeSlider->setValue( offsetSecs );
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleSliderMoved(int value)
|
||||
{
|
||||
std::cout << "Slider moved to " << value << std::endl;
|
||||
_curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) );
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleTimeUpdated(void)
|
||||
{
|
||||
std::cout << "Time updated to " << _mediaPlayer->currentTime() << std::endl;
|
||||
|
||||
if (_mediaPlayer->currentTime() > 0 && _mediaPlayer->currentTime() < _currentParameters.getInputMediaFile().getDuration().total_seconds())
|
||||
{
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
|
||||
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)
|
||||
{
|
||||
_mediaPlayer->setVolume( value / 100. );
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleClose(void)
|
||||
{
|
||||
_close.emit();
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleParametersEdit(void)
|
||||
{
|
||||
|
||||
_dialog = new VideoParametersDialog("Parameters");
|
||||
_dialog->load(_currentParameters);
|
||||
|
||||
_dialog->show();
|
||||
|
||||
_dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone);
|
||||
}
|
||||
|
||||
void
|
||||
VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)
|
||||
{
|
||||
assert(_dialog != nullptr);
|
||||
|
||||
if (code == Wt::WDialog::Accepted)
|
||||
{
|
||||
_dialog->save( _currentParameters );
|
||||
|
||||
// TODO SYNC current offset with player?
|
||||
// HACK use slider current value
|
||||
handlePlayOffset( _timeSlider->value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef __VIDEO_MEDIA_PLAYER_WIDGET_HPP
|
||||
#define __VIDEO_MEDIA_PLAYER_WIDGET_HPP
|
||||
|
||||
#include <Wt/WDialog>
|
||||
#include <Wt/WSlider>
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WLink>
|
||||
|
||||
#include "VideoParametersDialog.hpp"
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
class VideoMediaPlayerWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
|
||||
Wt::Signal<void>& close() { return _close; };
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// Signals
|
||||
Wt::Signal<void> _close;
|
||||
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
|
||||
// Player controls
|
||||
void handlePlayOffset(int offsetSecs);
|
||||
void handleTimeUpdated(void);
|
||||
void handleSliderMoved(int value);
|
||||
void handleFullscreen(void);
|
||||
void handleVolumeSliderMoved(int value);
|
||||
|
||||
void handleClose(void);
|
||||
|
||||
void handleParametersEdit(void);
|
||||
void handleParametersDone(Wt::WDialog::DialogCode);
|
||||
|
||||
// Core
|
||||
Wt::WMediaPlayer* _mediaPlayer;
|
||||
AvConvTranscodeStreamResource* _mediaResource;
|
||||
Wt::WLink _mediaInternalLink;
|
||||
|
||||
// Controls
|
||||
Transcode::Parameters _currentParameters;
|
||||
Wt::WPushButton* _playBtn;
|
||||
Wt::WPushButton* _pauseBtn;
|
||||
Wt::WSlider* _timeSlider;
|
||||
Wt::WSlider* _volumeSlider;
|
||||
Wt::WText* _curTime;
|
||||
Wt::WText* _duration;
|
||||
|
||||
VideoParametersDialog* _dialog;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WLabel>
|
||||
#include <Wt/WTable>
|
||||
|
||||
#include "VideoParametersDialog.hpp"
|
||||
|
||||
using namespace Transcode;
|
||||
|
||||
static const std::list<Stream::Type> streamTypes = {Stream::Video, Stream::Audio, Stream::Subtitle};
|
||||
|
||||
VideoParametersDialog::VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent)
|
||||
: Wt::WDialog(windowTitle, parent)
|
||||
{
|
||||
|
||||
Wt::WTable* layout = new Wt::WTable(contents());
|
||||
int row = 0;
|
||||
|
||||
{
|
||||
Wt::WLabel* label = new Wt::WLabel("Format");
|
||||
_outputFormat = new Wt::WComboBox();
|
||||
label->setBuddy(_outputFormat);
|
||||
|
||||
layout->elementAt(row, 0)->addWidget(label);
|
||||
layout->elementAt(row, 1)->addWidget(_outputFormat);
|
||||
|
||||
_outputFormatModel = new Wt::WStringListModel(_outputFormat);
|
||||
|
||||
std::vector<Format> formats = Format::get( Format::Video );
|
||||
|
||||
for(std::size_t idFormat = 0; idFormat < formats.size(); ++idFormat)
|
||||
{
|
||||
_outputFormatModel->addString(formats[idFormat].getDesc());
|
||||
_outputFormatModel->setData(idFormat, 0, formats[idFormat].getEncoding(), Wt::UserRole);
|
||||
}
|
||||
|
||||
_outputFormat->setModel(_outputFormatModel);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
createStreamWidgets("Video", Stream::Video, layout);
|
||||
createStreamWidgets("Audio", Stream::Audio, layout);
|
||||
createStreamWidgets("Subtitles", Stream::Subtitle, layout);
|
||||
|
||||
Wt::WPushButton *ok = new Wt::WPushButton("Apply", contents());
|
||||
ok->clicked().connect(this, &Wt::WDialog::accept);
|
||||
|
||||
Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", contents());
|
||||
cancel->clicked().connect(this, &Wt::WDialog::reject);
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Transcode::Stream::Type type, Wt::WTable* layout)
|
||||
{
|
||||
int row = layout->rowCount();
|
||||
|
||||
Wt::WLabel* label = new Wt::WLabel(labelString);
|
||||
Wt::WComboBox *combo = new Wt::WComboBox();
|
||||
label->setBuddy(combo);
|
||||
|
||||
layout->elementAt(row, 0)->addWidget(label);
|
||||
layout->elementAt(row, 1)->addWidget(combo);
|
||||
|
||||
Wt::WStringListModel* model = new Wt::WStringListModel(combo);
|
||||
|
||||
combo->setModel(model);
|
||||
|
||||
_streamSelection.insert( std::make_pair(type, std::make_pair(combo, model) ) );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoParametersDialog::handleApply()
|
||||
{
|
||||
_apply.emit();
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Stream>& streams)
|
||||
{
|
||||
for (std::size_t idStream = 0; idStream < streams.size(); ++idStream)
|
||||
{
|
||||
const Stream& stream = streams[idStream];
|
||||
|
||||
std::ostringstream oss;
|
||||
if (!stream.getLanguage().empty())
|
||||
oss << "[" << stream.getLanguage() << "] ";
|
||||
|
||||
oss << stream.getDesc();
|
||||
|
||||
model->addString(oss.str());
|
||||
model->setData(idStream, 0, stream.getId(), Wt::UserRole);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::selectStream(const Wt::WStringListModel* model, Stream::Id streamId, Wt::WComboBox* combo)
|
||||
{
|
||||
for (int idStream = 0; idStream < model->rowCount(); ++idStream)
|
||||
{
|
||||
Stream::Id id = boost::any_cast<Stream::Id>( model->data( model->index(idStream, 0), Wt::UserRole));
|
||||
if (id == streamId)
|
||||
{
|
||||
combo->setCurrentIndex(idStream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoParametersDialog::load(const Transcode::Parameters& parameters)
|
||||
{
|
||||
// Select proper encoding
|
||||
for (int row = 0; row < _outputFormat->count(); ++row)
|
||||
{
|
||||
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(row,0), Wt::UserRole));
|
||||
if (encoding == parameters.getOutputFormat().getEncoding()) {
|
||||
_outputFormat->setCurrentIndex(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current selected input streams
|
||||
Parameters::StreamMap streamMap = parameters.getInputStreams();;
|
||||
|
||||
// Populate the combox with the available streams
|
||||
// And then show the selected one
|
||||
BOOST_FOREACH(Stream::Type streamType, streamTypes)
|
||||
{
|
||||
Wt::WComboBox* combo = _streamSelection[streamType].first;
|
||||
Wt::WStringListModel* model = _streamSelection[streamType].second;
|
||||
|
||||
addStreams(model, parameters.getInputMediaFile().getStreams( streamType ) );
|
||||
|
||||
selectStream(model, streamMap[streamType], combo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
VideoParametersDialog::save(Transcode::Parameters& parameters)
|
||||
{
|
||||
std::cout << "Grabbing user input!" << std::endl;
|
||||
|
||||
// Get encoder used
|
||||
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(_outputFormat->currentIndex(), 0), Wt::UserRole));
|
||||
parameters.setOutputFormat( Format::get(encoding) );
|
||||
|
||||
// Get stream selected, if any
|
||||
BOOST_FOREACH(Stream::Type streamType, streamTypes)
|
||||
{
|
||||
Wt::WComboBox* combo = _streamSelection[streamType].first;
|
||||
Wt::WStringListModel* model = _streamSelection[streamType].second;
|
||||
|
||||
if (combo->currentIndex() >= 0)
|
||||
{
|
||||
Stream::Id streamId = boost::any_cast<Stream::Id>( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole));
|
||||
|
||||
parameters.selectInputStream(streamType, streamId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef VIDEO_PARAMETER_DIALOG
|
||||
#define VIDEO_PARAMETER_DIALOG
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <Wt/WSignal>
|
||||
#include <Wt/WDialog>
|
||||
#include <Wt/WComboBox>
|
||||
#include <Wt/WStringListModel>
|
||||
#include <Wt/WString>
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
|
||||
class VideoParametersDialog : public Wt::WDialog
|
||||
{
|
||||
public:
|
||||
// parameters to be edited
|
||||
VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent = 0);
|
||||
|
||||
// Populates widget contents using these paramaters
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
|
||||
// Save widgets contents into these parameters
|
||||
void save(Transcode::Parameters& parameters);
|
||||
|
||||
// Signal to be emitted if parameters are changed
|
||||
Wt::Signal<void>& apply() { return _apply; }
|
||||
|
||||
private:
|
||||
|
||||
void handleApply(void);
|
||||
|
||||
// Stream handling
|
||||
void createStreamWidgets(const Wt::WString& label, Transcode::Stream::Type type, Wt::WTable* layout);
|
||||
void addStreams(Wt::WStringListModel* model, const std::vector<Transcode::Stream>& streams);
|
||||
void selectStream(const Wt::WStringListModel* model, Transcode::Stream::Id streamId, Wt::WComboBox* combo);
|
||||
|
||||
Wt::Signal<void> _apply;
|
||||
|
||||
Wt::WComboBox* _outputFormat;
|
||||
Wt::WStringListModel* _outputFormatModel;
|
||||
|
||||
std::map<Transcode::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <Wt/WApplication>
|
||||
#include <Wt/WEnvironment>
|
||||
|
||||
#include "VideoWidget.hpp"
|
||||
|
||||
|
||||
VideoWidget::VideoWidget(Wt::WContainerWidget* parent )
|
||||
: Wt::WContainerWidget(parent)
|
||||
{
|
||||
|
||||
_videoDbWidget = new VideoDatabaseWidget(this);
|
||||
|
||||
_videoDbWidget->playVideo().connect(this, &VideoWidget::playVideo);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoWidget::search(const std::string& searchText)
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
void
|
||||
VideoWidget::backToList(void)
|
||||
{
|
||||
if (_mediaPlayer)
|
||||
delete _mediaPlayer;
|
||||
|
||||
_videoDbWidget->setHidden(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoWidget::playVideo(boost::filesystem::path p)
|
||||
{
|
||||
std::cout << "Want to play video " << p << "'" << std::endl;
|
||||
try {
|
||||
|
||||
// TODO get user's encoding preference
|
||||
Transcode::InputMediaFile inputFile(p);
|
||||
|
||||
Transcode::Format::Encoding encoding;
|
||||
|
||||
if (Wt::WApplication::instance()->environment().agentIsChrome())
|
||||
encoding = Transcode::Format::WEBMV;
|
||||
else
|
||||
encoding = Transcode::Format::FLV;
|
||||
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding), 128000, 500000);
|
||||
|
||||
_mediaPlayer = new VideoMediaPlayerWidget(parameters, this);
|
||||
_mediaPlayer->close().connect(this, &VideoWidget::backToList);
|
||||
|
||||
_videoDbWidget->setHidden(true);
|
||||
}
|
||||
catch( std::exception& e) {
|
||||
std::cerr <<"Caught exception while loading '" << p << "': " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef VIDEO_WIDGET_HPP
|
||||
#define VIDEO_WIDGET_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
|
||||
#include "video/VideoMediaPlayerWidget.hpp"
|
||||
#include "video/VideoDatabaseWidget.hpp"
|
||||
|
||||
class VideoWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
VideoWidget(Wt::WContainerWidget* parent = 0);
|
||||
|
||||
void search(const std::string& searchText);
|
||||
|
||||
private:
|
||||
|
||||
void backToList(void);
|
||||
void playVideo(boost::filesystem::path p);
|
||||
|
||||
VideoDatabaseWidget* _videoDbWidget;
|
||||
VideoMediaPlayerWidget* _mediaPlayer;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user