diff --git a/src/Makefile.am b/src/Makefile.am index e8304d03..b849c8d4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,6 +40,7 @@ lms_SOURCES = \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/CoverResource.cpp \ + $(srcdir)/ui/resource/TranscodeResource.cpp \ $(srcdir)/ui/settings/Settings.cpp \ $(srcdir)/ui/settings/SettingsAccountFormView.cpp \ $(srcdir)/ui/settings/SettingsAudioFormView.cpp \ @@ -60,5 +61,4 @@ lms_SOURCES += \ endif lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) - lms_LDADD=$(MAGICKXX_LIBS) diff --git a/src/av/AvTranscoder.cpp b/src/av/AvTranscoder.cpp index a9d8171a..94e0a4e2 100644 --- a/src/av/AvTranscoder.cpp +++ b/src/av/AvTranscoder.cpp @@ -29,22 +29,55 @@ namespace Av { #define LMS_LOG_TRANSCODE(sev) LMS_LOG(TRANSCODE, INFO) << "[" << _id << "] - " +struct EncodingInfo +{ + Encoding encoding; + std::string mimetype; + int id; +}; + +static std::vector encodingInfos = +{ + {Encoding::MP3, "audio/mp3", 0}, + {Encoding::OGA, "audio/ogg", 1}, + {Encoding::OGV, "video/ogg", 2}, + {Encoding::WEBMA, "audio/webm", 3}, + {Encoding::WEBMV, "video/webm", 4}, + {Encoding::M4A, "audio/mp4", 5}, + {Encoding::M4V, "video/mp4", 6}, +}; + std::string encoding_to_mimetype(Encoding encoding) { - switch(encoding) + for (auto encodingInfo : encodingInfos) { - case Encoding::MP3: return "audio/mp3"; - case Encoding::OGA: return "audio/ogg"; - case Encoding::OGV: return "video/ogg"; - case Encoding::WEBMA: return "audio/webm"; - case Encoding::WEBMV: return "video/webm"; - case Encoding::FLA: return "audio/x-flv"; - case Encoding::FLV: return "video/x-flv"; - case Encoding::M4A: return "audio/mp4"; - case Encoding::M4V: return "video/mp4"; + if (encodingInfo.encoding == encoding) + return encodingInfo.mimetype; } - return ""; + throw std::logic_error("encoding_to_mimetype failed!"); +} + +int encoding_to_int(Encoding encoding) +{ + for (auto encodingInfo : encodingInfos) + { + if (encodingInfo.encoding == encoding) + return encodingInfo.id; + } + + throw std::logic_error("encoding_to_int failed!"); +} + +Encoding encoding_from_int(int encodingId) +{ + for (auto encodingInfo : encodingInfos) + { + if (encodingInfo.id == encodingId) + return encodingInfo.encoding; + } + + throw std::logic_error("encoding_from_int failed!"); } // TODO, parametrize? @@ -129,6 +162,7 @@ Transcoder::start() // in order not to block the whole forked process args.push_back("-loglevel"); args.push_back("quiet"); + args.push_back("-nostdin"); // input Offset if (_parameters.getOffset().total_seconds() > 0) @@ -265,8 +299,8 @@ Transcoder::start() _child = std::make_shared(); - const redi::pstreams::pmode mode = redi::pstreams::pstdout; // | redi::pstreams::pstderr; - _child->open(avConvPath.string(), args, mode); + // Caution: stdin must have been closed before + _child->open(avConvPath.string(), args); if (!_child->is_open()) { LMS_LOG_TRANSCODE(DEBUG) << "Exec failed!"; @@ -279,8 +313,8 @@ Transcoder::start() return false; } - LMS_LOG_TRANSCODE(DEBUG) << "Stream opened!"; } + LMS_LOG_TRANSCODE(DEBUG) << "Stream opened!"; return true; } @@ -291,14 +325,31 @@ Transcoder::process(std::vector& output, std::size_t maxSize) if (!_child || _isComplete) return; + if (_child->out().fail()) + { + LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED 2"; + } + + if (_child->out().eof()) + { + LMS_LOG_TRANSCODE(DEBUG) << "Stdout ENDED 2"; + } + output.resize(maxSize); + LMS_LOG_TRANSCODE(DEBUG) << "Reading up to " << output.size() << " bytes"; + //Read on the output stream _child->out().read(reinterpret_cast(&output[0]), maxSize); output.resize(_child->out().gcount()); LMS_LOG_TRANSCODE(DEBUG) << "Read " << output.size() << " bytes"; + if (_child->out().fail()) + { + LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED"; + } + if (_child->out().eof()) { LMS_LOG_TRANSCODE(DEBUG) << "Stdout EOF!"; diff --git a/src/av/AvTranscoder.hpp b/src/av/AvTranscoder.hpp index 3f7a71f7..7289715a 100644 --- a/src/av/AvTranscoder.hpp +++ b/src/av/AvTranscoder.hpp @@ -47,6 +47,8 @@ enum class Encoding }; std::string encoding_to_mimetype(Encoding encoding); +int encoding_to_int(Encoding encoding); +Encoding encoding_from_int(int encoding); class TranscodeParameters { diff --git a/src/main/main.cpp b/src/main/main.cpp index 149d7cbc..228678ff 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -41,6 +41,8 @@ int main(int argc, char* argv[]) try { + // Make pstream work with ffmpeg + close(STDIN_FILENO); Wt::WServer server(argv[0]); server.setServerConfiguration (argc, argv); diff --git a/src/ui/resource/TranscodeResource.cpp b/src/ui/resource/TranscodeResource.cpp new file mode 100644 index 00000000..3573bfbc --- /dev/null +++ b/src/ui/resource/TranscodeResource.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include + +#include "logger/Logger.hpp" + +#include "TranscodeResource.hpp" + +namespace UserInterface { + +TranscodeResource::TranscodeResource(Database::Handler& db, Wt::WObject *parent) +: Wt::WResource(parent), +_db(db) +{ + LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE"; +} + +TranscodeResource:: ~TranscodeResource() +{ + LMS_LOG(UI, DEBUG) << "DESTRUCTING RESOURCE"; + beingDeleted(); +} + +std::string +TranscodeResource::getUrl(Database::Track::id_type trackId, Av::Encoding encoding, std::size_t offset, std::vector streamIds) const +{ + std::string res = url()+ "&trackid=" + std::to_string(trackId) + "&encoding=" + std::to_string(Av::encoding_to_int(encoding)); + if (!streamIds.empty()) + { + for (std::size_t streamId : streamIds) + res += "&stream=" + std::to_string(streamId); + } + res += "&offset=" + std::to_string(offset); + return res; +} + +void +TranscodeResource::handleRequest(const Wt::Http::Request& request, + Wt::Http::Response& response) +{ + // Retrieve parameters + const std::string *trackIdStr = request.getParameter("trackid"); + const std::string *offsetStr = request.getParameter("offset"); + const std::string *encodingStr = request.getParameter("encoding"); + std::vector streams = request.getParameterValues ("stream"); + + LMS_LOG(UI, DEBUG) << "Handling new request..."; + + try + { + std::shared_ptr transcoder; + + // First, see if this request is for a continuation + Wt::Http::ResponseContinuation *continuation = request.continuation(); + if (continuation) + { + LMS_LOG(UI, DEBUG) << "Continuation!"; + transcoder = boost::any_cast >(continuation->data()); + if (!transcoder) + { + LMS_LOG(UI, DEBUG) << "No transcoder set -> abort!"; + return; + } + } + else + { + LMS_LOG(UI, DEBUG) << "No continuation yet"; + + if (!trackIdStr + || !offsetStr + || !encodingStr) + { + LMS_LOG(UI, ERROR) << "Missing transcode parameter"; + return; + } + + Database::Track::id_type trackId = std::stol(*trackIdStr); + + // transactions are not thread safe + std::unique_lock lock(_mutex); + + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + + if (!track/* || !user */) + { + LMS_LOG(UI, ERROR) << "Missing track or user"; + return; + } + + Av::TranscodeParameters parameters; + + // TODO + parameters.setOffset(boost::posix_time::seconds(std::stol(*offsetStr))); + parameters.setEncoding(Av::encoding_from_int(std::stol(*encodingStr))); + parameters.setBitrate(Av::Stream::Type::Audio, 96000); + for (std::string strStream: streams) + { + LMS_LOG(UI, DEBUG) << "Added stream " << std::stol(strStream); + parameters.addStream(std::stol(strStream)); + } + + LMS_LOG(UI, DEBUG) << "Offset set to " << parameters.getOffset(); + transcoder = std::make_shared(track->getPath(), parameters); + + transaction.commit(); + + LMS_LOG(UI, DEBUG) << "Mime type set to '" << Av::encoding_to_mimetype(Av::Encoding::MP3); + response.setMimeType( Av::encoding_to_mimetype(Av::Encoding::MP3) ); + + if (!transcoder->start()) + { + LMS_LOG(UI, ERROR) << "Cannot start transcoder"; + return; + } + LMS_LOG(UI, DEBUG) << "Transcoder started"; + } + + LMS_LOG(UI, DEBUG) << "processing data?"; + + if (!transcoder->isComplete()) + { + std::vector data; + data.reserve(_bufferSize); + + LMS_LOG(UI, DEBUG) << "Processing data from transcoder..."; + transcoder->process(data, _bufferSize); + LMS_LOG(UI, DEBUG) << "Processing data from transcoder DONE"; + + // Give the client all the output data + response.out().write(reinterpret_cast(&data[0]), data.size()); + + LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete(); + + if (!response.out()) + LMS_LOG(UI, ERROR) << "Write failed!"; + } + + if (!transcoder->isComplete() && response.out()) { + continuation = response.createContinuation(); + continuation->setData(transcoder); + LMS_LOG(UI, DEBUG) << "Continuation set to " << continuation; + } + else + LMS_LOG(UI, DEBUG) << "No more data!"; + } + catch (std::invalid_argument& e) + { + LMS_LOG(UI, ERROR) << "Invalid argument: " << e.what(); + } +} + +} // namespace UserInterface + + diff --git a/src/ui/resource/TranscodeResource.hpp b/src/ui/resource/TranscodeResource.hpp new file mode 100644 index 00000000..e4714250 --- /dev/null +++ b/src/ui/resource/TranscodeResource.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include + +#include "av/AvTranscoder.hpp" + +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { + +class TranscodeResource : public Wt::WResource +{ + public: + TranscodeResource(Database::Handler& db, Wt::WObject *parent); + ~TranscodeResource(); + + std::string getUrl(Database::Track::id_type trackId, Av::Encoding encoding = Av::Encoding::OGA, size_t offset_secs = 0, std::vector streamIds = {}) const; + + void handleRequest(const Wt::Http::Request& request, + Wt::Http::Response& response); + + private: + + std::mutex _mutex; + Database::Handler& _db; + + static const std::size_t _bufferSize = 65536; +}; + +} // namespace UserInterface + + + + diff --git a/test/Makefile.am b/test/Makefile.am index 2d08aae0..8025dcab 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,7 +1,7 @@ TESTS = database-basics database-integrity sql-query database-user -check_PROGRAMS = database-basics database-integrity sql-query database-user test-wt test-avmetadata +check_PROGRAMS = database-basics database-integrity sql-query database-user test-wt test-avmetadata test-avtranscoder test-wt-audio database_basics_SOURCES = \ $(srcdir)/CheckDbBasics.cpp \ @@ -60,17 +60,48 @@ sql_query_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src test_wt_SOURCES = TestWt.cpp - test_wt_CXXFLAGS=-std=c++11 -Wall -Wextra +test_wt_audio_SOURCES = TestWtAudio.cpp\ + $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/utils/Utils.cpp \ + $(top_srcdir)/src/metadata/AvFormat.cpp \ + $(top_srcdir)/src/av/AvInfo.cpp \ + $(top_srcdir)/src/av/AvTranscoder.cpp \ + $(top_srcdir)/src/cover/CoverArtGrabber.cpp \ + $(top_srcdir)/src/database/Artist.cpp \ + $(top_srcdir)/src/database/Playlist.cpp \ + $(top_srcdir)/src/database/Track.cpp \ + $(top_srcdir)/src/database/DatabaseHandler.cpp \ + $(top_srcdir)/src/database/MediaDirectory.cpp \ + $(top_srcdir)/src/database/Release.cpp \ + $(top_srcdir)/src/database/SearchFilter.cpp \ + $(top_srcdir)/src/database/SqlQuery.cpp \ + $(top_srcdir)/src/database/User.cpp \ + $(top_srcdir)/src/database/Video.cpp \ + $(top_srcdir)/src/image/Image.cpp \ + $(top_srcdir)/src/ui/resource/CoverResource.cpp \ + $(top_srcdir)/src/ui/resource/TranscodeResource.cpp + +test_wt_audio_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src $(MAGICKXX_CFLAGS) +test_wt_audio_LDADD=$(MAGICKXX_LIBS) + test_avmetadata_SOURCES = TestAvMetadata.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ $(top_srcdir)/src/utils/Utils.cpp \ $(top_srcdir)/src/metadata/AvFormat.cpp \ - $(top_srcdir)/src/av/AvInfo.cpp \ - $(top_srcdir)/src/av/AvTranscoder.cpp + $(top_srcdir)/src/av/AvInfo.cpp test_avmetadata_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src +test_avtranscoder_SOURCES = TestAvTranscoder.cpp \ + $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/utils/Utils.cpp \ + $(top_srcdir)/src/av/AvInfo.cpp \ + $(top_srcdir)/src/av/AvTranscoder.cpp + +test_avtranscoder_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src + + diff --git a/test/TestAvTranscoder.cpp b/test/TestAvTranscoder.cpp new file mode 100644 index 00000000..f5a7e932 --- /dev/null +++ b/test/TestAvTranscoder.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include + +#include "av/AvInfo.hpp" +#include "av/AvTranscoder.hpp" + +int main(int argc, char *argv[]) +{ + if (argc != 2) + { + std::cerr << "Usage: " << std::endl; + return EXIT_FAILURE; + } + + try + { + Av::AvInit(); + Av::Transcoder::init(); + + // Make pstream work with ffmpeg + close(STDIN_FILENO); + + Av::TranscodeParameters parameters; + parameters.setEncoding(Av::Encoding::MP3); + parameters.setOffset( boost::posix_time::seconds(0) ); + parameters.setBitrate( Av::Stream::Type::Audio, 160000 ); +// parameters.addStream(0); + + Av::Transcoder transcoder(argv[1], parameters); + + if (!transcoder.start()) + throw std::runtime_error("transcoder.start failed!"); + + while (!transcoder.isComplete()) + { + std::vector data; + std::cout << "Processing ..." << std::endl; + transcoder.process(data, 65536); + std::cout << "Processing done" << std::endl; + } + + std::cout << "Complete!" << std::endl; + + return EXIT_SUCCESS; + } + catch (std::exception& e) + { + std::cerr << "Caught exception: " << e.what(); + return EXIT_FAILURE; + } + +} + diff --git a/test/TestWtAudio.cpp b/test/TestWtAudio.cpp new file mode 100644 index 00000000..4cf7cebc --- /dev/null +++ b/test/TestWtAudio.cpp @@ -0,0 +1,311 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "database/DatabaseHandler.hpp" + +#include "ui/resource/TranscodeResource.hpp" +#include "ui/resource/CoverResource.hpp" + + +class InputRange : public Wt::WWebWidget +{ + public: + InputRange(Wt::WContainerWidget *parent = 0) + : Wt::WWebWidget(parent) + { + setHtmlTagName("input"); + setAttributeValue("type", "range"); + } + + Wt::DomElementType domElementType() const + { + return Wt::DomElement_INPUT; + } +}; + + +Wt::WString MyPlayerTemplate = "${prev} ${play-pause} ${next} ${cover} ${artist} ${track} ${release} ${curtime} ${seekbar} ${duration} ${volume}"; + +class MyPlayer : public Wt::WContainerWidget +{ + public: + + void playbackComplete(void) + { + // Switch to the next track + } + + void loadTrack(Database::Track::id_type trackId) + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + if (!track) + { + std::cerr << "no track for this id!" << std::endl; + return; + } + + _trackName->setText(Wt::WString::fromUTF8(track->getName())); + _artistName->setText( Wt::WString::fromUTF8(track->getArtist()->getName())); + _releaseName->setText( Wt::WString::fromUTF8(track->getRelease()->getName())); + _cover->setImageLink(_coverResource->getTrackUrl(trackId, 64)); + _trackDuration->setText( boost::posix_time::to_simple_string( track->getDuration() )); + + // Analyse track, select the best media stream + Av::MediaFile mediaFile(track->getPath()); + + if (!mediaFile.open() || !mediaFile.scan()) + { + std::cerr << "cannot open file '" << track->getPath() << std::endl; + return; + } + + + int audioBestStreamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio); + std::vector streams; + if (audioBestStreamId != -1) + streams.push_back(audioBestStreamId); + + this->doJavaScript("\ + document.lms.audio.state = \"loaded\";\ + document.lms.audio.seekbar.min = " + std::to_string(0) + ";\ + document.lms.audio.seekbar.max = " + std::to_string(track->getDuration().total_seconds()) + ";\ + document.lms.audio.seekbar.value = 0;\ + document.lms.audio.seekbar.disabled = false;\ + document.lms.audio.offset = 0;\ + document.lms.audio.curTime = 0;\ + "); + + _audio->pause(); + _audio->clearSources(); + _audio->addSource(_transcodeResource->getUrl(trackId, Av::Encoding::MP3, 0, streams)); + _audio->play(); + } + + + MyPlayer(Database::Handler &db, Wt::WContainerWidget *parent = 0) + : Wt::WContainerWidget(parent), + _db(db) + { + + _transcodeResource = new UserInterface::TranscodeResource(db, this); + _coverResource = new UserInterface::CoverResource(db, this); + + Wt::WTemplate *t = new Wt::WTemplate(MyPlayerTemplate, this); + + _audio = new Wt::WAudio(this); + + _cover = new Wt::WImage(); + t->bindWidget("cover", _cover); + _cover->setImageLink(_coverResource->getUnknownTrackUrl(64)); + + InputRange *seekbar = new InputRange(); + t->bindWidget("seekbar", seekbar); + + _trackName = new Wt::WText(); + t->bindWidget("track", _trackName); + + _artistName = new Wt::WText(); + t->bindWidget("artist", _artistName); + + _releaseName = new Wt::WText(); + t->bindWidget("release", _releaseName); + + InputRange *volumeSlider = new InputRange(); + t->bindWidget("volume", volumeSlider); + + Wt::WPushButton *prevBtn = new Wt::WPushButton("<<"); + t->bindWidget("prev", prevBtn); + + Wt::WPushButton *nextBtn = new Wt::WPushButton(">>"); + t->bindWidget("next", nextBtn); + + Wt::WPushButton *playPauseBtn = new Wt::WPushButton("play/pause"); + t->bindWidget("play-pause", playPauseBtn); + + Wt::WText *trackCurrentTime = new Wt::WText("00:00"); + t->bindWidget("curtime", trackCurrentTime); + + _trackDuration = new Wt::WText("00:00"); + t->bindWidget("duration", _trackDuration); + + //Wt::WTemplate *_volumeSlider = new Wt::WTemplate(VolumeSliderTemplate, this); + + this->doJavaScript( +"\ + document.lms = {};\ + document.lms.audio = {};\ + document.lms.audio.audio = " + _audio->jsRef() + ";\ + document.lms.audio.seekbar = " + seekbar->jsRef() +";\ + document.lms.audio.volumeSlider = " + volumeSlider->jsRef() + ";\ + document.lms.audio.curTimeText = " + trackCurrentTime->jsRef() + ";\ + document.lms.audio.playPause = " + playPauseBtn->jsRef() + ";\ +\ + document.lms.audio.offset = 0;\ + document.lms.audio.curTime = 0;\ + document.lms.audio.state = \"init\";\ + document.lms.audio.volume = 1;\ +\ + document.lms.audio.seekbar.value = 0;\ + document.lms.audio.seekbar.disabled = true;\ +\ + document.lms.audio.volumeSlider.min = 0;\ + document.lms.audio.volumeSlider.max = 100;\ + document.lms.audio.volumeSlider.value = 100;\ +\ + function updateUI() {\ + document.lms.audio.curTimeText.innerHTML = document.lms.audio.curTime;\ + document.lms.audio.seekbar.value = document.lms.audio.curTime;\ + }\ +\ + var mouseDown = 0;\ + function seekMouseDown(e) {\ + ++mouseDown;\ + }\ + function seekMouseUp(e) {\ + --mouseDown;\ + }\ +\ + function seeking(e) {\ + if (document.lms.audio.state == \"init\")\ + return;\ +\ + document.lms.audio.curTimeText.innerHTML = document.lms.audio.seekbar.value;\ + }\ +\ + function seek(e) {\ + if (document.lms.audio.state == \"init\")\ + return;\ +\ + document.lms.audio.audio.pause(); \ + document.lms.audio.offset = parseInt(document.lms.audio.seekbar.value);\ + document.lms.audio.curTime = document.lms.audio.seekbar.value;\ + var audioSource = document.lms.audio.audio.getElementsByTagName(\"source\")[0];\ + var src = audioSource.src;\ + src = src.slice(0, src.lastIndexOf(\"=\") + 1);\ + audioSource.src = src + document.lms.audio.seekbar.value;\ + document.lms.audio.audio.load(); \ + document.lms.audio.audio.play(); \ + document.lms.audio.curTimeText.innerHTML = ~~document.lms.audio.curTime + \" \";\ + }\ +\ + function volumeChanged() {\ + document.lms.audio.audio.volume = document.lms.audio.volumeSlider.value / 100;\ + }\ +\ + function updateCurTime() {\ + document.lms.audio.curTime = document.lms.audio.offset + ~~document.lms.audio.audio.currentTime; \ + if (mouseDown == 0)\ + updateUI();\ + } \ +\ + function playPause() {\ + if (document.lms.audio.state == \"init\") \ + return;\ +\ + if (document.lms.audio.audio.paused)\ + document.lms.audio.audio.play();\ + else\ + document.lms.audio.audio.pause();\ +\ + }\ +\ + document.lms.audio.audio.addEventListener('timeupdate', updateCurTime); \ + document.lms.audio.seekbar.addEventListener('change', seek);\ + document.lms.audio.seekbar.addEventListener('input', seeking);\ + document.lms.audio.seekbar.addEventListener('mousedown', seekMouseDown);\ + document.lms.audio.seekbar.addEventListener('mouseup', seekMouseUp);\ + document.lms.audio.volumeSlider.addEventListener('input', volumeChanged);\ + document.lms.audio.playPause.addEventListener('click', playPause);\ +" +); + } + + UserInterface::TranscodeResource* _transcodeResource; + UserInterface::CoverResource* _coverResource; + Database::Handler& _db; + Wt::WAudio* _audio; + Wt::WText* _trackDuration; + Wt::WText* _trackName; + Wt::WText* _artistName; + Wt::WText* _releaseName; + Wt::WImage* _cover; +}; + +class TestApplication : public Wt::WApplication +{ + public: + TestApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool) + : Wt::WApplication(env) + , _db(connectionPool) + { + Wt::WBootstrapTheme *bootstrapTheme = new Wt::WBootstrapTheme(this); + bootstrapTheme->setVersion(Wt::WBootstrapTheme::Version3); + bootstrapTheme->setResponsive(true); + setTheme(bootstrapTheme); + + Wt::WLineEdit* trackSelector = new Wt::WLineEdit(); + MyPlayer* player = new MyPlayer(_db); + + trackSelector->changed().connect(std::bind([=] { + player->loadTrack(Wt::asNumber(trackSelector->valueText())); + })); + + root()->addWidget(trackSelector); + root()->addWidget(player); + } + + private: + Database::Handler _db; + +}; + +static Wt::WApplication *createTestApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool ) +{ + return new TestApplication(env, connectionPool); +} + +int main(int argc, char *argv[]) +{ + try + { + Av::AvInit(); + Av::Transcoder::init(); + + Wt::WServer server(argv[0]); + server.setServerConfiguration (argc, argv); + + // Make pstream work with ffmpeg + close(STDIN_FILENO); + + Database::Handler::configureAuth(); + std::unique_ptr connectionPool( Database::Handler::createConnectionPool("/var/lms/lms.db")); + + server.addEntryPoint(Wt::Application, boost::bind(createTestApplication, _1, boost::ref(*connectionPool))); + + server.start(); + + Wt::WServer::waitForShutdown(argv[0]); + + server.stop(); + + return EXIT_SUCCESS; + } + catch (std::exception& e) + { + std::cerr << "Caught exception: " << e.what(); + return EXIT_FAILURE; + } + +} +