[Merge] lp:~justinmcp/media-hub/1239432 into lp:media-hub
Jim Hodapp
jim.hodapp at canonical.com
Tue Nov 4 23:33:54 UTC 2014
Review: Needs Fixing code
Looks really good. Only one minor inline comment to address. Would like to help you test it after you get it built in a silo.
Diff comments:
> === modified file 'CMakeLists.txt'
> --- CMakeLists.txt 2014-10-14 16:21:47 +0000
> +++ CMakeLists.txt 2014-11-03 00:57:41 +0000
> @@ -6,8 +6,8 @@
> set(UBUNTU_MEDIA_HUB_VERSION_MINOR 0)
> set(UBUNTU_MEDIA_HUB_VERSION_PATCH 0)
>
> -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wall -pedantic -Wextra -fPIC -pthread")
> -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Werror -Wall -fno-strict-aliasing -pedantic -Wextra -fPIC -pthread")
> +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fPIC -pthread")
> +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -fno-strict-aliasing -Wextra -fPIC -pthread")
>
> set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
>
>
> === modified file 'debian/control'
> --- debian/control 2014-10-23 21:25:38 +0000
> +++ debian/control 2014-11-03 00:57:41 +0000
> @@ -28,6 +28,8 @@
> gstreamer1.0-libav,
> libgstreamer1.0-dev,
> pkg-config,
> + qtbase5-dev,
> + libtelepathy-qt5-dev,
> Standards-Version: 3.9.5
> Homepage: https://launchpad.net/media-hub
> # If you aren't a member of ~phablet-team but need to upload packaging changes,
>
> === modified file 'src/core/media/CMakeLists.txt'
> --- src/core/media/CMakeLists.txt 2014-10-14 16:21:47 +0000
> +++ src/core/media/CMakeLists.txt 2014-11-03 00:57:41 +0000
> @@ -7,6 +7,8 @@
>
> file(GLOB MPRIS_HEADERS mpris/ *.h)
>
> +add_subdirectory(call-monitor)
> +
> add_library(
> media-hub-common SHARED
>
> @@ -90,7 +92,7 @@
> media-hub-service
>
> media-hub-common
> -
> + call-monitor
> ${DBUS_LIBRARIES}
> ${DBUS_CPP_LDFLAGS}
> ${GLog_LIBRARY}
> @@ -113,6 +115,7 @@
>
> media-hub-service
> media-hub-client
> + call-monitor
>
> ${DBUS_LIBRARIES}
> ${DBUS_CPP_LDFLAGS}
>
> === added directory 'src/core/media/call-monitor'
> === added file 'src/core/media/call-monitor/CMakeLists.txt'
> --- src/core/media/call-monitor/CMakeLists.txt 1970-01-01 00:00:00 +0000
> +++ src/core/media/call-monitor/CMakeLists.txt 2014-11-03 00:57:41 +0000
> @@ -0,0 +1,23 @@
> +SET (CMAKE_INCLUDE_CURRENT_DIR ON)
> +SET (CMAKE_AUTOMOC ON)
> +
> +find_package(Qt5Core REQUIRED)
> +find_package(PkgConfig REQUIRED)
> +pkg_check_modules(TP_QT5 REQUIRED TelepathyQt5)
> +
> +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Wextra -fPIC -pthread")
> +
> +include_directories(
> + ${TP_QT5_INCLUDE_DIRS}
> +)
> +
> +add_library(call-monitor STATIC
> + call_monitor.cpp
> + qtbridge.cpp
> +)
> +
> +target_link_libraries(call-monitor
> + ${TP_QT5_LIBRARIES}
> +)
> +
> +qt5_use_modules(call-monitor Core DBus)
>
> === added file 'src/core/media/call-monitor/call_monitor.cpp'
> --- src/core/media/call-monitor/call_monitor.cpp 1970-01-01 00:00:00 +0000
> +++ src/core/media/call-monitor/call_monitor.cpp 2014-11-03 00:57:41 +0000
> @@ -0,0 +1,207 @@
> +/*
> + * Copyright (C) 2014 Canonical Ltd
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU Lesser General Public License version 3 as
> + * published by the Free Software Foundation.
> + *
> + * This program 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 Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Author: Justin McPherson <justin.mcpherson at canonical.com>
> + */
> +
> +
> +#include "call_monitor.h"
> +
> +#include "qtbridge.h"
> +#include <TelepathyQt/AccountManager>
> +#include <TelepathyQt/SimpleCallObserver>
> +#include <TelepathyQt/PendingOperation>
> +#include <TelepathyQt/PendingReady>
> +#include <TelepathyQt/PendingAccount>
> +
> +#include <list>
> +#include <mutex>
> +#include <syslog.h>
I didn't see anything that would require this include. Is it really necessary?
> +
> +
> +namespace {
> +class TelepathyCallMonitor : public QObject
> +{
> + Q_OBJECT
> +public:
> + TelepathyCallMonitor(const Tp::AccountPtr& account):
> + mAccount(account),
> + mCallObserver(Tp::SimpleCallObserver::create(mAccount)) {
> + connect(mCallObserver.data(), SIGNAL(callStarted(Tp::CallChannelPtr)), SIGNAL(offHook()));
> + connect(mCallObserver.data(), SIGNAL(callEnded(Tp::CallChannelPtr,QString,QString)), SIGNAL(onHook()));
> + connect(mCallObserver.data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)), SIGNAL(offHook()));
> + connect(mCallObserver.data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)), SIGNAL(onHook()));
> + }
> +
> +Q_SIGNALS:
> + void offHook();
> + void onHook();
> +
> +private:
> + Tp::AccountPtr mAccount;
> + Tp::SimpleCallObserverPtr mCallObserver;
> +};
> +
> +
> +class TelepathyBridge : public QObject
> +{
> + Q_OBJECT
> +public:
> + TelepathyBridge():
> + QObject(0) {
> + Tp::registerTypes();
> +
> + QTimer::singleShot(0, this, SLOT(accountManagerSetup()));
> + }
> +
> + ~TelepathyBridge() {
> + for (std::list<TelepathyCallMonitor*>::iterator it = mCallMonitors.begin();
> + it != mCallMonitors.end();
> + ++it) {
> + delete *it;
> + }
> + }
> +
> + void on_change(const std::function<void(CallMonitor::State)>& func) {
> + std::lock_guard<std::mutex> l(cb_lock);
> + cb = func;
> + }
> +
> +private Q_SLOTS:
> + void accountManagerSetup() {
> + mAccountManager = Tp::AccountManager::create();
> + connect(mAccountManager->becomeReady(),
> + SIGNAL(finished(Tp::PendingOperation*)),
> + SLOT(accountManagerReady(Tp::PendingOperation*)));
> + }
> +
> + void accountManagerReady(Tp::PendingOperation* operation) {
> + if (operation->isError()) {
> + std::cerr << "TelepathyBridge: Operation failed (accountManagerReady)" << std::endl;
> + QTimer::singleShot(1000, this, SLOT(accountManagerSetup())); // again
> + return;
> + }
> +
> + Q_FOREACH(const Tp::AccountPtr& account, mAccountManager->allAccounts()) {
> + connect(account->becomeReady(Tp::Account::FeatureCapabilities),
> + SIGNAL(finished(Tp::PendingOperation*)),
> + SLOT(accountReady(Tp::PendingOperation*)));
> + }
> +
> + connect(mAccountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(newAccount(Tp::AccountPtr)));
> + }
> +
> + void newAccount(const Tp::AccountPtr& account)
> + {
> + connect(account->becomeReady(Tp::Account::FeatureCapabilities),
> + SIGNAL(finished(Tp::PendingOperation*)),
> + SLOT(accountReady(Tp::PendingOperation*)));
> + }
> +
> + void accountReady(Tp::PendingOperation* operation) {
> + if (operation->isError()) {
> + std::cerr << "TelepathyAccount: Operation failed (accountReady)" << std::endl;
> + return;
> + }
> +
> + Tp::PendingReady* pendingReady = qobject_cast<Tp::PendingReady*>(operation);
> + if (pendingReady == 0) {
> + std::cerr << "Rejecting account because could not understand ready status" << std::endl;
> + return;
> + }
> +
> + checkAndAddAccount(Tp::AccountPtr::qObjectCast(pendingReady->proxy()));
> + }
> +
> + void offHook()
> + {
> + std::lock_guard<std::mutex> l(cb_lock);
> + if (cb)
> + cb(CallMonitor::OffHook);
> + }
> +
> + void onHook()
> + {
> + std::lock_guard<std::mutex> l(cb_lock);
> + if (cb)
> + cb(CallMonitor::OnHook);
> + }
> +
> +private:
> + std::mutex cb_lock;
> + std::function<void (CallMonitor::State)> cb;
> + Tp::AccountManagerPtr mAccountManager;
> + std::list<TelepathyCallMonitor*> mCallMonitors;
> +
> + void checkAndAddAccount(const Tp::AccountPtr& account)
> + {
> + Tp::ConnectionCapabilities caps = account->capabilities();
> +
> + // anything call like, perhaps overkill?
> + if (caps.audioCalls() || caps.videoCalls() || caps.videoCallsWithAudio() || caps.streamedMediaCalls()) {
> + auto tcm = new TelepathyCallMonitor(account);
> + connect(tcm, SIGNAL(offHook()), SLOT(offHook()));
> + connect(tcm, SIGNAL(onHook()), SLOT(onHook()));
> + mCallMonitors.push_back(tcm);
> + }
> + }
> +};
> +}
> +
> +class CallMonitorPrivate
> +{
> +public:
> + CallMonitorPrivate() {
> + try {
> + std::thread([this]() {
> + qt::core::world::build_and_run(0, nullptr, [this]() {
> + qt::core::world::enter_with_task([this]() {
> + mBridge = new TelepathyBridge();
> + });
> + });
> + }).detach();
> + } catch(const std::system_error& error) {
> + std::cerr << "exception(std::system_error) in CallMonitor thread start" << error.what() << std::endl;
> + } catch(...) {
> + std::cerr << "exception(...) in CallMonitor thread start" << std::endl;
> + }
> + }
> +
> + ~CallMonitorPrivate() {
> + qt::core::world::destroy();
> + }
> +
> + TelepathyBridge *mBridge;
> +};
> +
> +
> +CallMonitor::CallMonitor():
> + d(new CallMonitorPrivate)
> +{
> +}
> +
> +CallMonitor::~CallMonitor()
> +{
> + delete d->mBridge;
> + delete d;
> +}
> +
> +void CallMonitor::on_change(const std::function<void(CallMonitor::State)>& func)
> +{
> + d->mBridge->on_change(func);
> +}
> +
> +#include "call_monitor.moc"
> +
>
> === added file 'src/core/media/call-monitor/call_monitor.h'
> --- src/core/media/call-monitor/call_monitor.h 1970-01-01 00:00:00 +0000
> +++ src/core/media/call-monitor/call_monitor.h 2014-11-03 00:57:41 +0000
> @@ -0,0 +1,41 @@
> +/*
> + * Copyright (C) 2014 Canonical Ltd
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU Lesser General Public License version 3 as
> + * published by the Free Software Foundation.
> + *
> + * This program 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 Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Author: Justin McPherson <justin.mcpherson at canonical.com>
> + */
> +
> +
> +#ifndef CALLMONITOR_H
> +#define CALLMONITOR_H
> +
> +#include <functional>
> +
> +class CallMonitorPrivate;
> +
> +class CallMonitor
> +{
> +public:
> + enum State { OffHook, OnHook };
> +
> + CallMonitor();
> + ~CallMonitor();
> +
> + void on_change(const std::function<void(CallMonitor::State)>& func);
> +
> +private:
> + CallMonitorPrivate *d;
> +};
> +
> +#endif // CALLMONITOR_H
>
> === added file 'src/core/media/call-monitor/qtbridge.cpp'
> --- src/core/media/call-monitor/qtbridge.cpp 1970-01-01 00:00:00 +0000
> +++ src/core/media/call-monitor/qtbridge.cpp 2014-11-03 00:57:41 +0000
> @@ -0,0 +1,186 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program 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 Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Jussi Pakkanen <jussi.pakkanen at canonical.com>
> + * Thomas Voß <thomas.voss at canonical.com>
> + */
> +
> +#include "qtbridge.h"
> +
> +#include<QCoreApplication>
> +#include<QNetworkAccessManager>
> +#include<QNetworkRequest>
> +#include<QNetworkReply>
> +#include<QThread>
> +#include<QDebug>
> +
> +#include <iostream>
> +
> +namespace
> +{
> +QCoreApplication* app = nullptr;
> +}
> +
> +namespace qt
> +{
> +namespace core
> +{
> +namespace world
> +{
> +namespace detail
> +{
> +QEvent::Type qt_core_world_task_event_type()
> +{
> + static QEvent::Type event_type = static_cast<QEvent::Type>(QEvent::registerEventType());
> + return event_type;
> +}
> +
> +class TaskEvent : public QEvent
> +{
> +public:
> + TaskEvent(const std::function<void()>& task)
> + : QEvent(qt_core_world_task_event_type()),
> + task(task)
> + {
> + }
> +
> + void run()
> + {
> + try
> + {
> + task();
> + promise.set_value();
> + } catch(...)
> + {
> + promise.set_exception(std::current_exception());
> + }
> + }
> +
> + std::future<void> get_future()
> + {
> + return promise.get_future();
> + }
> +
> +private:
> + std::function<void()> task;
> + std::promise<void> promise;
> +};
> +
> +class TaskHandler : public QObject
> +{
> + Q_OBJECT
> +
> +public:
> + TaskHandler(QObject* parent) : QObject(parent)
> + {
> + }
> +
> + bool event(QEvent* e);
> +};
> +
> +
> +
> +void createCoreApplicationInstanceWithArgs(int argc, char** argv)
> +{
> + app = new QCoreApplication(argc, argv);
> +}
> +
> +void destroyCoreApplicationInstace()
> +{
> + delete app;
> +}
> +
> +QCoreApplication* coreApplicationInstance()
> +{
> + return app;
> +}
> +
> +TaskHandler* task_handler()
> +{
> + static TaskHandler* instance = new TaskHandler(coreApplicationInstance());
> + return instance;
> +}
> +
> +bool TaskHandler::event(QEvent *e)
> +{
> + if (e->type() != qt_core_world_task_event_type())
> + return QObject::event(e);
> +
> + auto te = dynamic_cast<TaskEvent*>(e);
> + if (te)
> + {
> + te->run();
> + return true;
> + }
> +
> + return false;
> +}
> +}
> +
> +void build_and_run(int argc, char** argv, const std::function<void()>& ready)
> +{
> + QThread::currentThread();
> + if (QCoreApplication::instance() != nullptr)
> + throw std::runtime_error(
> + "qt::core::world::build_and_run: "
> + "There is already a QCoreApplication running.");
> +
> + detail::createCoreApplicationInstanceWithArgs(argc, argv);
> +
> + detail::task_handler()->moveToThread(
> + detail::coreApplicationInstance()->thread());
> +
> + // Signal to other worlds that we are good to go.
> + ready();
> +
> + detail::coreApplicationInstance()->exec();
> +
> + // Someone has called quit and we clean up on the correct thread here.
> + detail::destroyCoreApplicationInstace();
> +}
> +
> +void destroy()
> +{
> + enter_with_task([]()
> + {
> + // We make sure that all tasks have completed before quitting the app.
> + QEventLoopLocker locker;
> + }).wait_for(std::chrono::seconds{1});
> +}
> +
> +std::future<void> enter_with_task(const std::function<void()>& task)
> +{
> + QCoreApplication* instance = QCoreApplication::instance();
> +
> + if (!instance)
> + {
> + throw std::runtime_error("Qt world has not been built before calling this function.");
> + }
> +
> + detail::TaskEvent* te = new detail::TaskEvent(task);
> + auto future = te->get_future();
> +
> + // We hand over ownership of te here. The event is deleted later after it has
> + // been processed by the event loop.
> + instance->postEvent(detail::task_handler(), te);
> +
> + return future;
> +}
> +
> +}
> +}
> +}
> +
> +#include "qtbridge.moc"
>
> === added file 'src/core/media/call-monitor/qtbridge.h'
> --- src/core/media/call-monitor/qtbridge.h 1970-01-01 00:00:00 +0000
> +++ src/core/media/call-monitor/qtbridge.h 2014-11-03 00:57:41 +0000
> @@ -0,0 +1,87 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program 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 Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Jussi Pakkanen <jussi.pakkanen at canonical.com>
> + * Thomas Voß <thomas.voss at canonical.com>
> + */
> +
> +#ifndef QT_CORE_WORLD_BRIDGE_H_
> +#define QT_CORE_WORLD_BRIDGE_H_
> +
> +#include <QObject>
> +
> +#include <functional>
> +#include <future>
> +#include <iostream>
> +
> +namespace qt
> +{
> +namespace core
> +{
> +namespace world
> +{
> +/**
> + * @brief Sets up the Qt core world and executes its event loop. Blocks until destroy() is called.
> + * @param argc Number of arguments in argv.
> + * @param argv Array of command-line arguments.
> + * @param ready Functor be called when the world has been setup and is about to be executed.
> + * @throw std::runtime_error in case of errors.
> + */
> +void build_and_run(int argc, char** argv, const std::function<void()>& ready);
> +
> +/**
> + * @brief Destroys the Qt core world and quits its event loop.
> + */
> +void destroy();
> +
> +/**
> + * @brief Enters the Qt core world and schedules the given task for execution.
> + * @param task The task to be executed in the Qt core world.
> + * @return A std::future that can be waited for to synchronize to the world's internal event loop.
> + */
> +std::future<void> enter_with_task(const std::function<void()>& task);
> +
> +
> +/**
> + * @brief Enters the Qt core world and schedules the given task for execution.
> + * @param task The task to be executed in the Qt core world.
> + * @return A std::future that can be waited for to get hold of the result of the task.
> + */
> +template<typename T>
> +inline std::future<T> enter_with_task_and_expect_result(const std::function<T()>& task)
> +{
> + std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>();
> + std::future<T> future = promise->get_future();
> +
> + auto t = [promise, task]()
> + {
> + try
> + {
> + promise->set_value(task());
> + } catch(...)
> + {
> + promise->set_exception(std::current_exception());
> + }
> + };
> +
> + enter_with_task(t);
> +
> + return future;
> +}
> +}
> +}
> +}
> +
> +#endif // QT_CORE_WORLD_BRIDGE_H_
>
> === modified file 'src/core/media/service_implementation.cpp'
> --- src/core/media/service_implementation.cpp 2014-10-15 14:20:03 +0000
> +++ src/core/media/service_implementation.cpp 2014-11-03 00:57:41 +0000
> @@ -20,6 +20,7 @@
> #include "service_implementation.h"
>
> #include "indicator_power_service.h"
> +#include "call-monitor/call_monitor.h"
> #include "player_configuration.h"
> #include "player_implementation.h"
>
> @@ -43,7 +44,8 @@
> Private()
> : resume_key(std::numeric_limits<std::uint32_t>::max()),
> keep_alive(io_service),
> - disp_cookie(0)
> + disp_cookie(0),
> + call_monitor(new CallMonitor)
> {
> bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::session));
> bus->install_executor(dbus::asio::make_executor(bus, io_service));
> @@ -127,6 +129,8 @@
> int disp_cookie;
> std::shared_ptr<dbus::Object> uscreen_session;
> MediaRecorderObserver *observer;
> + std::unique_ptr<CallMonitor> call_monitor;
> + std::list<media::Player::PlayerKey> paused_sessions;
> };
>
> media::ServiceImplementation::ServiceImplementation() : d(new Private())
> @@ -148,6 +152,17 @@
> if (!notifying)
> resume_multimedia_session();
> });
> +
> + d->call_monitor->on_change([this](CallMonitor::State state) {
> + switch (state) {
> + case CallMonitor::OffHook:
> + pause_all_multimedia_sessions();
> + break;
> + case CallMonitor::OnHook:
> + resume_paused_multimedia_sessions();
> + break;
> + }
> + });
> }
>
> media::ServiceImplementation::~ServiceImplementation()
> @@ -216,13 +231,21 @@
> if (player->playback_status() == Player::playing
> && player->audio_stream_role() == media::Player::multimedia)
> {
> - d->resume_key = key;
> - cout << "Will resume playback of Player with key: " << d->resume_key << endl;
> + d->paused_sessions.push_back(key);
> player->pause();
> }
> });
> }
>
> +void media::ServiceImplementation::resume_paused_multimedia_sessions()
> +{
> + std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this](const media::Player::PlayerKey& key) {
> + player_for_key(key)->play();
> + });
> +
> + d->paused_sessions.clear();
> +}
> +
> void media::ServiceImplementation::resume_multimedia_session()
> {
> if (not has_player_for_key(d->resume_key))
>
> === modified file 'src/core/media/service_implementation.h'
> --- src/core/media/service_implementation.h 2014-09-09 14:48:51 +0000
> +++ src/core/media/service_implementation.h 2014-11-03 00:57:41 +0000
> @@ -42,6 +42,7 @@
>
> private:
> void pause_all_multimedia_sessions();
> + void resume_paused_multimedia_sessions();
> void resume_multimedia_session();
>
> struct Private;
>
> === modified file 'tests/unit-tests/CMakeLists.txt'
> --- tests/unit-tests/CMakeLists.txt 2014-10-14 16:21:47 +0000
> +++ tests/unit-tests/CMakeLists.txt 2014-11-03 00:57:41 +0000
> @@ -25,6 +25,7 @@
>
> media-hub-common
> media-hub-client
> + call-monitor
> media-hub-test-framework
>
> ${CMAKE_THREAD_LIBS_INIT}
>
--
https://code.launchpad.net/~justinmcp/media-hub/1239432/+merge/240387
Your team Ubuntu Phablet Team is subscribed to branch lp:media-hub.
More information about the Ubuntu-reviews
mailing list