/* * 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 "logger/Logger.hpp" #include "MediaRequestHandler.hpp" #include "database/AudioTypes.hpp" namespace Remote { namespace Server { MediaRequestHandler::MediaRequestHandler(Database::Handler& db) : _db(db) {} bool MediaRequestHandler::process(const MediaRequest& request, MediaResponse& response) { bool res = false; switch (request.type()) { case MediaRequest::TypeMediaPrepare: if (request.has_prepare()) { if (request.prepare().has_audio()) res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result()); else if (request.prepare().has_video()) LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!"; else LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!"; if (res) response.set_type(MediaResponse::TypePrepareResult); } else LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!"; break; case MediaRequest::TypeMediaGetPart: if (request.has_get_part()) { res = processGetPart(request.get_part(), *response.mutable_part_result()); if (res) response.set_type(MediaResponse::TypePartResult); } else LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!"; break; case MediaRequest::TypeMediaTerminate: if (request.has_terminate()) { res = processTerminate(request.terminate(), *response.mutable_terminate_result()); if (res) response.set_type(MediaResponse::TypePartResult); } else LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!"; break; default: LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled MediaRequest type = " << request.type(); } return res; } bool MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response) { std::size_t bitrate; Transcode::Format::Encoding format; switch( request.codec_type()) { case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break; default: LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type(); return false; } switch( request.bitrate() ) { case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break; case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break; case MediaRequest::Prepare::AudioBitrate_96_kbps: bitrate = 96000; break; case MediaRequest::Prepare::AudioBitrate_128_kbps: bitrate = 128000; break; case MediaRequest::Prepare::AudioBitrate_192_kbps: bitrate = 192000; break; case MediaRequest::Prepare::AudioBitrate_256_kbps: bitrate = 256000; break; default: LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled bitrate type = " << request.bitrate(); return false; } // TODO use user's bitrate limits! // TODO limit transcoder number by user? if (_transcoders.size() + 1 > _maxTranscoders) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Transcoder limit reached!" << std::endl; // Just answer an empty response, dont delete existing trasncode jobs return true; } try { Wt::Dbo::Transaction transaction( _db.getSession()); Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() ); if (!track) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Requested track does not exist" << std::endl; // Track does no longer exist, just answer an empty response return true; } Transcode::InputMediaFile inputFile(track->getPath()); Transcode::Parameters parameters(inputFile, Transcode::Format::get( format )); parameters.setBitrate( Transcode::Stream::Audio, bitrate); std::shared_ptr transcoder = std::make_shared( parameters ); // now get a unique id (relative to this connection!) uint32_t handle = _curHandle++; assert(_transcoders.find(handle) == _transcoders.end()); _transcoders[handle] = transcoder; response.set_handle(handle); LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Set up new transcode, handle = " << handle; } catch(std::exception& e) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what(); return false; } return true; } bool MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response) { std::size_t dataSize = request.requested_data_size(); if (dataSize > _maxPartSize) dataSize = _maxPartSize; if (_transcoders.find(request.handle()) == _transcoders.end()) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle(); return true; } std::shared_ptr transcoder = _transcoders[request.handle()]; while (!transcoder->isComplete() && transcoder->getOutputData().size() < dataSize) transcoder->process(); LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << transcoder->getOutputData().size(); Transcode::AvConvTranscoder::data_type::iterator itEnd; if (transcoder->getOutputData().size() > dataSize) itEnd = transcoder->getOutputData().begin() + dataSize; else itEnd = transcoder->getOutputData().end(); std::copy(transcoder->getOutputData().begin(), itEnd, std::back_inserter(*response.mutable_data())); // Consume sent bytes transcoder->getOutputData().erase(transcoder->getOutputData().begin(), itEnd); return true; } bool MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response) { LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle(); if (_transcoders.find(request.handle()) == _transcoders.end()) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle(); return true; } else { _transcoders.erase(request.handle()); } return true; } } // namespace Remote } // namespace Server