diff --git a/approot/templates.xml b/approot/templates.xml index 76e1f019..c2e348de 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -4,7 +4,7 @@ ${navbar-top class="main-nav"} ${contents class="container"} - ${player} + ${player class="Lms-player"} diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 47303070..b259d5a1 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -3,3 +3,7 @@ font-weight: bold; } +.Lms-player { + background-color: grey; +} + diff --git a/src/Makefile.am b/src/Makefile.am index 66a1efd3..b5c510a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,7 +39,6 @@ lms_SOURCES = \ $(srcdir)/ui/explore/ReleasesView.cpp \ $(srcdir)/ui/explore/ReleaseView.cpp \ $(srcdir)/ui/explore/TracksView.cpp \ - $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.cpp \ $(srcdir)/utils/Config.cpp \ diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp index 90aa10d4..43f2332f 100644 --- a/src/av/AvInfo.cpp +++ b/src/av/AvInfo.cpp @@ -201,7 +201,7 @@ MediaFile::getStreams(Stream::Type type) const return res; } -int +boost::optional MediaFile::getBestStreamId(Stream::Type type) const { if (_context == nullptr) @@ -215,7 +215,7 @@ MediaFile::getBestStreamId(Stream::Type type) const case Stream::Type::Video: avMediaType = AVMEDIA_TYPE_VIDEO; break; case Stream::Type::Subtitle: avMediaType = AVMEDIA_TYPE_SUBTITLE; break; default: - return -1; + return boost::none; } int res = av_find_best_stream(_context, @@ -227,7 +227,7 @@ MediaFile::getBestStreamId(Stream::Type type) const if (res < 0) { LMS_LOG(AV, ERROR) << "Cannot find best stream for type " << streamType_to_string(type); - return -1; + return boost::none; } return res; diff --git a/src/av/AvInfo.hpp b/src/av/AvInfo.hpp index 377fd6fd..1bb92e1b 100644 --- a/src/av/AvInfo.hpp +++ b/src/av/AvInfo.hpp @@ -35,6 +35,7 @@ extern "C" #include #include +#include #include #include //no i/o just types @@ -84,7 +85,7 @@ class MediaFile std::map getMetaData(void); std::vector getStreams(Stream::Type type) const; - int getBestStreamId(Stream::Type type) const; // -1 if failure/unknown + boost::optional getBestStreamId(Stream::Type type) const; // none if failure/unknown bool hasAttachedPictures(void) const; std::vector getAttachedPictures(std::size_t nbMaxPictures) const; diff --git a/src/av/AvTranscoder.cpp b/src/av/AvTranscoder.cpp index 3a82fcf8..44c7c33f 100644 --- a/src/av/AvTranscoder.cpp +++ b/src/av/AvTranscoder.cpp @@ -39,11 +39,8 @@ 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) @@ -139,10 +136,10 @@ Transcoder::start() args.push_back("-nostdin"); // input Offset - if (_parameters.getOffset().total_seconds() > 0) + if (_parameters.offset.total_seconds() > 0) { args.push_back("-ss"); - args.push_back(std::to_string(_parameters.getOffset().total_seconds())); + args.push_back(std::to_string(_parameters.offset.total_seconds())); } // Input file @@ -151,20 +148,24 @@ Transcoder::start() // Output bitrates args.push_back("-b:a"); - args.push_back(std::to_string(_parameters.getBitrate(Stream::Type::Audio))); -// if (_parameters.getOutputFormat().getType() == Format::Video) -// oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video); + args.push_back(std::to_string(_parameters.bitrate)); - // Stream mapping - for (int streamId : _parameters.getSelectedStreamIds()) + // Stream mapping, if set + if (_parameters.stream) { - // 0 means the first input file args.push_back("-map"); - args.push_back("0:" + std::to_string(streamId)); + args.push_back("0:" + std::to_string(*_parameters.stream)); } + // Strip metadata + args.push_back("-map_metadata"); + args.push_back("-1"); + + // Skip video flows (including covers) + args.push_back("-vn"); + // Codecs and formats - switch( _parameters.getEncoding()) + switch( _parameters.encoding) { case Encoding::MP3: args.push_back("-f"); @@ -178,20 +179,6 @@ Transcoder::start() args.push_back("ogg"); break; - case Encoding::OGV: - args.push_back("-acodec"); - args.push_back("libvorbis"); - args.push_back("-ac"); - args.push_back("2"); - args.push_back("-ar"); - args.push_back("44100"); - args.push_back("-vcodec"); - args.push_back("libtheora"); - args.push_back("-threads"); - args.push_back("4"); - args.push_back("-f"); - args.push_back("ogg"); - break; case Encoding::WEBMA: args.push_back("-codec:a"); args.push_back("libvorbis"); @@ -199,21 +186,6 @@ Transcoder::start() args.push_back("webm"); break; - case Encoding::WEBMV: - args.push_back("-acodec"); - args.push_back("libvorbis"); - args.push_back("-ac"); - args.push_back("2"); - args.push_back("-ar"); - args.push_back("44100"); - args.push_back("-vcodec"); - args.push_back("libvpx"); - args.push_back("-threads"); - args.push_back("4"); - args.push_back("-f"); - args.push_back("webm"); - break; - case Encoding::M4A: args.push_back("-acodec"); args.push_back("aac"); @@ -223,21 +195,6 @@ Transcoder::start() args.push_back("experimental"); break; - case Encoding::M4V: - args.push_back("-acodec"); - args.push_back("aac"); - args.push_back("-strict"); - args.push_back("experimental"); - args.push_back("-ac"); - args.push_back("2"); - args.push_back("-ar"); - args.push_back("-44100"); - args.push_back("-vcodec"); - args.push_back("libx264"); - args.push_back("-f"); - args.push_back("m4v"); - break; - default: return false; } diff --git a/src/av/AvTranscoder.hpp b/src/av/AvTranscoder.hpp index cf958fd3..73e10ec5 100644 --- a/src/av/AvTranscoder.hpp +++ b/src/av/AvTranscoder.hpp @@ -25,8 +25,8 @@ #include #include //no i/o just types -#include #include +#include #include "AvInfo.hpp" @@ -39,42 +39,23 @@ enum class Encoding OGV, MP3, WEBMA, - WEBMV, M4A, - M4V, }; std::string encoding_to_mimetype(Encoding encoding); int encoding_to_int(Encoding encoding); Encoding encoding_from_int(int encoding); -class TranscodeParameters +struct TranscodeParameters { - public: + Encoding encoding = Encoding::MP3; + boost::optional stream = boost::none; // Id of the stream to be transcoded (auto detect by default) + std::size_t bitrate = 128000; + boost::posix_time::time_duration offset = boost::posix_time::seconds(0); - // Setters - void setEncoding(Encoding encoding) { _encoding = encoding; } - void setOffset(boost::posix_time::time_duration offset) {_offset = offset; } - void setBitrate(Stream::Type type, std::size_t bitrate) { _outputBitrate[type] = bitrate; } - - // Manually add the streams to be transcoded - // If no stream is added, input streams are selected automatically - void addStream(int inputStreamId) { _selectedStreams.insert(inputStreamId); } - - // Getters - Encoding getEncoding(void) const { return _encoding; } - boost::posix_time::time_duration getOffset(void) const { return _offset; } - std::set getSelectedStreamIds(void) const { return _selectedStreams; } - std::size_t getBitrate(Stream::Type type) { return _outputBitrate[type]; } - - private: - Encoding _encoding = Encoding::MP3; - boost::posix_time::time_duration _offset = boost::posix_time::seconds(0); - std::set _selectedStreams; - std::map _outputBitrate = { {Stream::Type::Audio, 0}, { Stream::Type::Video, 0}, { Stream::Type::Subtitle, 0} }; + TranscodeParameters() {} }; - class Transcoder { public: @@ -89,7 +70,7 @@ class Transcoder bool start(); void process(std::vector& output, std::size_t maxSize); - bool isComplete(void) { return _isComplete; } + bool isComplete(void) const { return _isComplete; } const TranscodeParameters& getParameters() const { return _parameters; } diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 1c95261c..5b28bcee 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -40,6 +40,9 @@ #include "admin/DatabaseSettingsView.hpp" #include "admin/AdminWizardView.hpp" +#include "resource/ImageResource.hpp" +#include "resource/TranscodeResource.hpp" + #include "LmsApplication.hpp" namespace UserInterface { @@ -356,6 +359,7 @@ LmsApplication::handleAuthEvent(void) // Events from the PlayQueue playqueue->playTrack.connect(player, &MediaPlayer::playTrack); + playqueue->playbackStop.connect(player, &MediaPlayer::stop); // Events from MediaScanner if (CurrentUser()->isAdmin()) diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index 59f0875a..5b7d336a 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -26,12 +26,13 @@ #include "database/DatabaseHandler.hpp" #include "scanner/MediaScanner.hpp" -#include "resource/ImageResource.hpp" -#include "resource/TranscodeResource.hpp" #include "Auth.hpp" namespace UserInterface { +class TranscodeResource; +class ImageResource; + class LmsApplication : public Wt::WApplication { public: diff --git a/src/ui/MediaPlayer.cpp b/src/ui/MediaPlayer.cpp index f891e04b..849ee478 100644 --- a/src/ui/MediaPlayer.cpp +++ b/src/ui/MediaPlayer.cpp @@ -21,15 +21,29 @@ #include #include +#include "av/AvInfo.hpp" #include "utils/Logger.hpp" +#include "resource/TranscodeResource.hpp" + +#include "LmsApplication.hpp" #include "MediaPlayer.hpp" + namespace UserInterface { MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent) { + _audio = new Wt::WAudio(this); + _audio->setOptions(Wt::WAudio::Autoplay); + _audio->setPreloadMode(Wt::WAudio::PreloadNone); + + _audio->ended().connect(std::bind([=] () + { + playbackEnded.emit(); + })); + auto player = new Wt::WTemplate(Wt::WString::tr("template-mediaplayer"), this); auto playPauseBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-play"), Wt::XHTMLText); @@ -66,9 +80,36 @@ MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent) } void -MediaPlayer::playTrack(Database::Track::id_type id) +MediaPlayer::playTrack(Database::Track::id_type trackId) { - LMS_LOG(UI, DEBUG) << "Playing track ID = " << id; + LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId; + + Wt::Dbo::Transaction transaction(DboSession()); + auto track = Database::Track::getById(DboSession(), trackId); + transaction.commit(); + + // Analyse track, select the best media stream + Av::MediaFile mediaFile(track->getPath()); + + if (!mediaFile.open() || !mediaFile.scan()) + { + LMS_LOG(UI, ERROR) << "Cannot open file '" << track->getPath(); + return; + } + + auto streamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio); + + _audio->pause(); + _audio->clearSources(); + _audio->addSource(LmsApp->getTranscodeResource()->getUrl(trackId, Av::Encoding::MP3, boost::posix_time::seconds(0), streamId)); + _audio->setPreloadMode(Wt::WAudio::PreloadNone); + _audio->play(); +} + +void +MediaPlayer::stop() +{ + _audio->pause(); } } // namespace UserInterface diff --git a/src/ui/MediaPlayer.hpp b/src/ui/MediaPlayer.hpp index 8cb013d6..8b36d8eb 100644 --- a/src/ui/MediaPlayer.hpp +++ b/src/ui/MediaPlayer.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include "database/Types.hpp" @@ -31,6 +32,7 @@ class MediaPlayer : public Wt::WContainerWidget public: MediaPlayer(Wt::WContainerWidget* parent = 0); + void stop(); void playTrack(Database::Track::id_type); // Signals @@ -38,6 +40,8 @@ class MediaPlayer : public Wt::WContainerWidget Wt::Signal playPrevious; Wt::Signal playNext; + private: + Wt::WAudio* _audio; }; } // namespace UserInterface diff --git a/src/ui/PlayQueueView.cpp b/src/ui/PlayQueueView.cpp index a839b993..e5bce0b4 100644 --- a/src/ui/PlayQueueView.cpp +++ b/src/ui/PlayQueueView.cpp @@ -93,6 +93,7 @@ PlayQueue::stop() { updateCurrentTrack(false); _trackPos.reset(); + playbackStop.emit(); } void diff --git a/src/ui/PlayQueueView.hpp b/src/ui/PlayQueueView.hpp index 13628cd9..18a0de34 100644 --- a/src/ui/PlayQueueView.hpp +++ b/src/ui/PlayQueueView.hpp @@ -47,6 +47,9 @@ class PlayQueue : public Wt::WContainerWidget // Signal emitted when a track is to be played Wt::Signal playTrack; + // Signal emitted when play has to be stopped + Wt::Signal playbackStop; + private: void addSome(); void updateInfo(); diff --git a/src/ui/explore/ArtistView.cpp b/src/ui/explore/ArtistView.cpp index 1f443339..ec21c004 100644 --- a/src/ui/explore/ArtistView.cpp +++ b/src/ui/explore/ArtistView.cpp @@ -28,6 +28,8 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" +#include "resource/ImageResource.hpp" + #include "LmsApplication.hpp" #include "Filters.hpp" #include "ArtistView.hpp" diff --git a/src/ui/explore/ReleaseView.cpp b/src/ui/explore/ReleaseView.cpp index 37c2487a..47018e67 100644 --- a/src/ui/explore/ReleaseView.cpp +++ b/src/ui/explore/ReleaseView.cpp @@ -28,6 +28,8 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" +#include "resource/ImageResource.hpp" + #include "LmsApplication.hpp" #include "Filters.hpp" #include "ReleaseView.hpp" diff --git a/src/ui/explore/ReleasesView.cpp b/src/ui/explore/ReleasesView.cpp index 6bf9dddf..3fceee00 100644 --- a/src/ui/explore/ReleasesView.cpp +++ b/src/ui/explore/ReleasesView.cpp @@ -28,6 +28,8 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" +#include "resource/ImageResource.hpp" + #include "LmsApplication.hpp" #include "Filters.hpp" #include "ReleasesView.hpp" diff --git a/src/ui/explore/TracksView.cpp b/src/ui/explore/TracksView.cpp index 4875e597..531623e7 100644 --- a/src/ui/explore/TracksView.cpp +++ b/src/ui/explore/TracksView.cpp @@ -27,6 +27,8 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" +#include "resource/ImageResource.hpp" + #include "LmsApplication.hpp" #include "Filters.hpp" #include "TracksView.hpp" diff --git a/src/ui/resource/AvConvTranscodeStreamResource.cpp b/src/ui/resource/AvConvTranscodeStreamResource.cpp deleted file mode 100644 index 528d9cba..00000000 --- a/src/ui/resource/AvConvTranscodeStreamResource.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2013 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 - -#include "utils/Logger.hpp" - -#include "AvConvTranscodeStreamResource.hpp" - -namespace UserInterface { - -AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent) -: Wt::WResource(parent), -_filePath(p), -_parameters( parameters ) -{ - LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE"; -} - -AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource() -{ - LMS_LOG(UI, DEBUG) << "DESTRUCTING RESOURCE"; - beingDeleted(); -} - -void -AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request, - Wt::Http::Response& response) -{ - LMS_LOG(UI, DEBUG) << "Handling new request..."; - - // see if this request is for a continuation: - Wt::Http::ResponseContinuation *continuation = request.continuation(); - - LMS_LOG(UI, DEBUG) << "Handling new request. Continuation = " << continuation; - - std::shared_ptr transcoder; - if (continuation) - transcoder = boost::any_cast >(continuation->data()); - - if (!transcoder) - { - LMS_LOG(UI, DEBUG) << "Launching transcoder"; - transcoder = std::make_shared( _filePath, _parameters); - - LMS_LOG(UI, DEBUG) << "Mime type set to '" << Av::encoding_to_mimetype(_parameters.getEncoding()); - response.setMimeType( Av::encoding_to_mimetype(_parameters.getEncoding()) ); - - if (!transcoder->start()) - { - LMS_LOG(UI, ERROR) << "Cannot start transcoder"; - return; - } - } - - if (!transcoder->isComplete()) - { - std::vector data; - data.reserve(_bufferSize); - - transcoder->process(data, _bufferSize); - - // 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!"; -} - -} // namespace UserInterface - - diff --git a/src/ui/resource/AvConvTranscodeStreamResource.hpp b/src/ui/resource/AvConvTranscodeStreamResource.hpp deleted file mode 100644 index b6fbbb94..00000000 --- a/src/ui/resource/AvConvTranscodeStreamResource.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2013 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 . - */ - -#ifndef AVCONV_TRANSCODE_STREAM_RESOURCE_HPP -#define AVCONV_TRANSCODE_STREAM_RESOURCE_HPP - -#include -#include - -#include - -#include "av/AvTranscoder.hpp" - -namespace UserInterface { - -class AvConvTranscodeStreamResource : public Wt::WResource -{ - public: - AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent = 0); - ~AvConvTranscodeStreamResource(); - - void handleRequest(const Wt::Http::Request& request, - Wt::Http::Response& response); - - private: - boost::filesystem::path _filePath; - Av::TranscodeParameters _parameters; - - static const std::size_t _bufferSize = 8192; -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/resource/TranscodeResource.cpp b/src/ui/resource/TranscodeResource.cpp index f12c6fad..428a68d2 100644 --- a/src/ui/resource/TranscodeResource.cpp +++ b/src/ui/resource/TranscodeResource.cpp @@ -41,15 +41,13 @@ TranscodeResource:: ~TranscodeResource() } std::string -TranscodeResource::getUrl(Database::Track::id_type trackId, Av::Encoding encoding, std::size_t offset, std::vector streamIds) const +TranscodeResource::getUrl(Database::Track::id_type trackId, Av::Encoding encoding, boost::posix_time::time_duration offset, boost::optional streamId) 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); + if (streamId) + res += "&stream=" + std::to_string(*streamId); + res += "&offset=" + std::to_string(offset.seconds()); + return res; } @@ -57,121 +55,107 @@ 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"); + std::shared_ptr transcoder; LMS_LOG(UI, DEBUG) << "Handling new request..."; - try + // First, see if this request is for a continuation + Wt::Http::ResponseContinuation *continuation = request.continuation(); + if (continuation) { - 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! " << continuation ; - transcoder = boost::any_cast >(continuation->data()); - if (!transcoder) - { - LMS_LOG(UI, ERROR) << "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 - { - Wt::WApplication::UpdateLock lock(LmsApplication::instance()); - - Wt::Dbo::Transaction transaction(_db.getSession()); - - Database::User::pointer user = _db.getCurrentUser(); - Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - - if (!track) - { - LMS_LOG(UI, ERROR) << "Missing track"; - return; - } - - if (!user) - { - LMS_LOG(UI, ERROR) << "Missing user"; - return; - } - - Av::TranscodeParameters parameters; - - parameters.setOffset(boost::posix_time::seconds(std::stol(*offsetStr))); - parameters.setEncoding(Av::encoding_from_int(std::stol(*encodingStr))); - parameters.setBitrate(Av::Stream::Type::Audio, user->getAudioBitrate() ); - 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); - } - - std::string mimeType = Av::encoding_to_mimetype(transcoder->getParameters().getEncoding()); - - LMS_LOG(UI, DEBUG) << "Mime type set to '" << mimeType << "'"; - response.setMimeType(mimeType); - - if (!transcoder->start()) - { - LMS_LOG(UI, ERROR) << "Cannot start transcoder"; - return; - } - - LMS_LOG(UI, DEBUG) << "Transcoder started"; - } - - if (!transcoder->isComplete()) - { - std::vector data; - data.reserve(_chunkSize); - - transcoder->process(data, _chunkSize); - - 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); - } - else - LMS_LOG(UI, DEBUG) << "No more data!"; + LMS_LOG(UI, DEBUG) << "Continuation! " << continuation ; + transcoder = boost::any_cast>(continuation->data()); } - catch (std::invalid_argument& e) + else { - LMS_LOG(UI, ERROR) << "Invalid argument: " << e.what(); + Database::Track::id_type trackId; + Av::TranscodeParameters parameters; + + LMS_LOG(UI, DEBUG) << "No continuation yet"; + try + { + auto trackIdStr = request.getParameter("trackid"); + if (!trackIdStr) + { + LMS_LOG(UI, ERROR) << "Missing trackid transcode parameter!"; + return; + } + trackId = std::stol(*request.getParameter("trackid")); + + auto offsetStr = request.getParameter("offset"); + if (offsetStr) + parameters.offset = boost::posix_time::seconds(std::stol(*offsetStr)); + + auto encodingStr = request.getParameter("encoding"); + if (encodingStr) + parameters.encoding = Av::encoding_from_int(std::stol(*encodingStr)); + + auto streamStr = request.getParameter("stream"); + if (streamStr) + parameters.stream = std::stol(*streamStr); + } + catch (std::exception &e) + { + LMS_LOG(UI, ERROR) << "Exception while handling URL parameters: " << e.what(); + return; + } + + // transactions are not thread safe + { + Wt::WApplication::UpdateLock lock(LmsApplication::instance()); + + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + + if (!track) + { + LMS_LOG(UI, ERROR) << "Missing track"; + return; + } + + parameters.bitrate = user->getAudioBitrate(); + transcoder = std::make_shared(track->getPath(), parameters); + } + + std::string mimeType = Av::encoding_to_mimetype(transcoder->getParameters().encoding); + + LMS_LOG(UI, DEBUG) << "Mime type set to '" << mimeType << "'"; + response.setMimeType(mimeType); + + if (!transcoder->start()) + { + LMS_LOG(UI, ERROR) << "Cannot start transcoder"; + return; + } + + LMS_LOG(UI, DEBUG) << "Transcoder started"; } + + if (!transcoder->isComplete()) + { + std::vector data; + data.reserve(_chunkSize); + + transcoder->process(data, _chunkSize); + + 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); + } + else + LMS_LOG(UI, DEBUG) << "No more data!"; } } // namespace UserInterface diff --git a/src/ui/resource/TranscodeResource.hpp b/src/ui/resource/TranscodeResource.hpp index 4ed7b4c5..a13f4e9b 100644 --- a/src/ui/resource/TranscodeResource.hpp +++ b/src/ui/resource/TranscodeResource.hpp @@ -21,6 +21,8 @@ #include +#include + #include #include "av/AvTranscoder.hpp" @@ -35,7 +37,7 @@ class TranscodeResource : public Wt::WResource 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; + std::string getUrl(Database::Track::id_type trackId, Av::Encoding encoding, boost::posix_time::time_duration offset, boost::optional stream = boost::none) const; void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response);