diff options
-rw-r--r-- | app/Dialer.qml | 1 | ||||
-rw-r--r-- | app/app.pri | 6 | ||||
-rw-r--r-- | app/app.pro | 11 | ||||
-rw-r--r-- | app/fileplayer.cpp | 290 | ||||
-rw-r--r-- | app/fileplayer.h | 62 | ||||
-rw-r--r-- | app/main.cpp | 7 | ||||
-rw-r--r-- | app/phone.cpp | 19 | ||||
-rw-r--r-- | app/phone.h | 6 | ||||
-rw-r--r-- | package/config.xml | 2 |
9 files changed, 382 insertions, 22 deletions
diff --git a/app/Dialer.qml b/app/Dialer.qml index 9dc91db..248bc6c 100644 --- a/app/Dialer.qml +++ b/app/Dialer.qml @@ -18,7 +18,6 @@ import QtQuick 2.6 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.0 -import QtMultimedia 5.5 import AGL.Demo.Controls 1.0 Item { diff --git a/app/app.pri b/app/app.pri index c3b1fd1..7a60d44 100644 --- a/app/app.pri +++ b/app/app.pri @@ -11,3 +11,9 @@ config_libhomescreen { } DESTDIR = $${OUT_PWD}/../package/root/bin + +copy_ringtone.target = $$DESTDIR/Phone.wav +copy_ringtone.depends = $$_PRO_FILE_PWD_/Phone.wav +copy_ringtone.commands = $(COPY_FILE) \"$$replace(copy_ringtone.depends, /, $$QMAKE_DIR_SEP)\" \"$$replace(copy_ringtone.target, /, $$QMAKE_DIR_SEP)\" +QMAKE_EXTRA_TARGETS += copy_ringtone +PRE_TARGETDEPS += $$copy_ringtone.target diff --git a/app/app.pro b/app/app.pro index d1f19d6..2a6a48b 100644 --- a/app/app.pro +++ b/app/app.pro @@ -1,15 +1,14 @@ TARGET = phone -QT = quickcontrols2 websockets multimedia +QT = quickcontrols2 websockets -SOURCES = main.cpp phone.cpp -HEADERS = phone.h numbertype.h +SOURCES = main.cpp phone.cpp fileplayer.cpp +HEADERS = phone.h numbertype.h fileplayer.h CONFIG += link_pkgconfig -PKGCONFIG += libhomescreen qlibwindowmanager qtappfw +PKGCONFIG += libhomescreen qlibwindowmanager qtappfw libafbwsc gstreamer-1.0 RESOURCES += \ phone.qrc \ - images/images.qrc \ - audio.qrc + images/images.qrc include(app.pri) diff --git a/app/fileplayer.cpp b/app/fileplayer.cpp new file mode 100644 index 0000000..e26a3e6 --- /dev/null +++ b/app/fileplayer.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fileplayer.h" +#include <string> +#include <cstring> +#include <iostream> +#include <mutex> +#include <condition_variable> +#include <json-c/json.h> + +#undef DEBUG + +struct set_role_data +{ + bool state; + std::string output; + std::condition_variable cv; +}; + +static void on_hangup(void *closure, struct afb_wsj1 *wsj) +{ +} + +static void on_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) +{ +} + +static void on_event(void* closure, const char* event, struct afb_wsj1_msg *msg) +{ +} + +static void on_reply(void *closure, struct afb_wsj1_msg *msg) +{ + struct set_role_data *data = (struct set_role_data*) closure; + struct json_object* reply; + + if(!(data && data->state)) + goto reply_done; + + // We opened the role, return the output + reply = afb_wsj1_msg_object_j(msg); + if(reply) { +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": reply = " << \ + json_object_to_json_string_ext(reply, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) << \ + std::endl; +#endif + struct json_object* response; + int rc = json_object_object_get_ex(reply, "response", &response); + if(rc) { + struct json_object* val; + rc = json_object_object_get_ex(response, "device_uri", &val); + if (rc && json_object_get_string_len(val)) { + const char* jres_pcm = json_object_get_string(val); + data->output = jres_pcm; +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": output = " << jres_pcm << std::endl; +#endif + } + } + } +reply_done: + // Signal reply is done + data->cv.notify_one(); +} + +static void *afb_loop_thread(struct sd_event* loop) +{ + for(;;) + sd_event_run(loop, 30000000); +} + +static void *gst_loop_thread(GMainLoop *loop) +{ + if(loop) + g_main_loop_run(loop); +} + +FilePlayer::FilePlayer(const int port, const std::string &token, const std::string &path, const std::string &role) : + m_path(path), + m_role(role) +{ + std::string uri; + + if(sd_event_new(&m_afb_loop) < 0) { + std::cerr << __FUNCTION__ << ": Failed to create event loop" << std::endl; + return; + } + + // Initialize interface for websocket + m_itf.on_hangup = on_hangup; + m_itf.on_call = on_call; + m_itf.on_event = on_event; + + uri = "ws://localhost:" + std::to_string(port) + "/api?token=" + token; +#ifdef DEBUG + std::cerr << "Using URI: " << uri << std::endl; +#endif + m_ws = afb_ws_client_connect_wsj1(m_afb_loop, uri.c_str(), &m_itf, NULL); + if(m_ws) { + m_afb_thread = std::thread(afb_loop_thread, m_afb_loop); + } else { + std::cerr << __FUNCTION__ << ": Failed to create websocket connection" << std::endl; + goto error; + } + + // Initialize GStreamer + gst_init(NULL, NULL); + + // Create elements we need + m_playbin = gst_element_factory_make("playbin", "play"); + m_alsa_sink = gst_element_factory_make("alsasink", NULL); + if(!(m_playbin && m_alsa_sink)) + goto error; + + // Set up bus callback + m_bus = gst_pipeline_get_bus(GST_PIPELINE(m_playbin)); + if(!m_bus) + goto error; + m_gst_loop = g_main_loop_new(NULL, FALSE); + if(!m_gst_loop) + goto error; + gst_bus_add_watch(m_bus, gstreamer_bus_callback, this); + + // Start thread to run glib main loop for gstreamer bus + m_gst_thread = std::thread(gst_loop_thread, m_gst_loop); + + m_valid = true; + return; +error: + gst_object_unref(m_playbin); + m_playbin = nullptr; + gst_object_unref(m_alsa_sink); + m_alsa_sink = nullptr; + gst_object_unref(m_bus); + m_bus = nullptr; + + if(m_gst_loop) { + g_main_loop_quit(m_gst_loop); + m_gst_loop = nullptr; + } + if(m_afb_loop) { + sd_event_unref(m_afb_loop); + m_afb_loop = nullptr; + } + return; +} + +FilePlayer::~FilePlayer(void) +{ + gst_element_set_state(m_playbin, GST_STATE_NULL); + gst_object_unref(m_playbin); + gst_object_unref(m_alsa_sink); + gst_object_unref(m_bus); + sd_event_unref(m_afb_loop); +} + +void FilePlayer::play(bool loop) +{ + std::string output; + + if(!m_valid || m_playing) + return; + + if(set_role_state(true, &output) != 0) + return; + + if(output.empty()) + return; + + m_playing = true; + m_looping = loop; + + g_object_set(m_alsa_sink, "device", output.c_str(), NULL); + g_object_set(m_playbin, "audio-sink", m_alsa_sink, NULL); + std::string uri = "file://" + m_path; + g_object_set(m_playbin, "uri", uri.c_str(), NULL); + + // Start playback + gst_element_set_state(m_playbin, GST_STATE_PLAYING); + + return; +} + +void FilePlayer::stop(void) +{ + if(!(m_valid && m_playing)) + return; + + // Stop playback + gst_element_set_state(m_playbin, GST_STATE_PAUSED); +} + +gboolean FilePlayer::gstreamer_bus_callback(GstBus *bus, GstMessage *msg, gpointer data) +{ + if(!data) + return TRUE; + + return static_cast<FilePlayer*>(data)->bus_callback(bus, msg); +} + +int FilePlayer::set_role_state(bool state, std::string *output) +{ + if(!m_valid) + return -1; + + set_role_data data; + data.state = state; + json_object *jsonData = json_object_new_object(); + json_object_object_add(jsonData, "action", json_object_new_string(state ? "open" : "close")); + int rc = afb_wsj1_call_j(m_ws, "ahl-4a", m_role.c_str(), jsonData, on_reply, (void*) &data); + if(rc >= 0) { + // Wait for response + std::mutex m; + std::unique_lock<std::mutex> lk(m); + data.cv.wait(lk); + if(state && output) + *output = data.output; + } else { + std::cerr << __FUNCTION__ << ": Failed to call ahl-4a/" << m_role.c_str() << std::endl; + } + return rc; +} + +gboolean FilePlayer::bus_callback(GstBus *bus, GstMessage *msg) +{ + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": GST_MESSAGE_EOS" << std::endl; +#endif + if(m_looping) { + // restart playback if at end + if (!gst_element_seek(m_playbin, + 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { + std::cerr << "Seek failed!" << std::endl; + } + } else { + // Move stream to paused state since we're done + gst_element_set_state(m_playbin, GST_STATE_PAUSED); + } + break; + case GST_MESSAGE_STATE_CHANGED: { + if((GstElement*) GST_MESSAGE_SRC(msg) != m_playbin) + break; + + GstState old_state, new_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, NULL); +#ifdef DEBUG + std::cerr << __FUNCTION__ << ": GST_MESSAGE_STATE_CHANGE: " << (int) old_state << " to " << (int) new_state << std::endl; +#endif + if(old_state == GST_STATE_PLAYING && new_state == GST_STATE_PAUSED) { + set_role_state(false); + + // Seek back to beginning so any subsequent play starts there + if (!gst_element_seek(m_playbin, + 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { + std::cerr << "Seek failed!" << std::endl; + } + + m_playing = false; + } else if(old_state == GST_STATE_READY && new_state == GST_STATE_NULL) { + // clean up + g_main_loop_quit(m_gst_loop); + } + break; + } + default: + break; + } + return TRUE; +} diff --git a/app/fileplayer.h b/app/fileplayer.h new file mode 100644 index 0000000..5f52370 --- /dev/null +++ b/app/fileplayer.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILEPLAYER_H +#define FILEPLAYER_H + +#include <string> +#include <thread> +#include <gst/gst.h> + +extern "C" +{ +#include <afb/afb-wsj1.h> +#include <afb/afb-ws-client.h> +#include <systemd/sd-event.h> +} + +class FilePlayer +{ +public: + FilePlayer(int port, const std::string &token, const std::string &path, const std::string &role); + ~FilePlayer(); + + void play(bool loop = false); + void stop(void); + + static gboolean gstreamer_bus_callback(GstBus *bus, GstMessage *msg, gpointer data); + +private: + std::string m_path; + std::string m_role; + struct afb_wsj1 *m_ws = nullptr; + struct afb_wsj1_itf m_itf; + std::thread m_afb_thread; + sd_event *m_afb_loop = nullptr; + std::thread m_gst_thread; + GMainLoop *m_gst_loop = nullptr; + GstElement *m_playbin = nullptr; + GstElement *m_alsa_sink = nullptr; + GstBus *m_bus = nullptr; + bool m_valid = false; + bool m_playing = false; + bool m_looping = false; + + int set_role_state(bool state, std::string *output = nullptr); + gboolean bus_callback(GstBus *bus, GstMessage *msg); +}; + +#endif // FILEPLAYER_H diff --git a/app/main.cpp b/app/main.cpp index 71868d8..91580a5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -30,6 +30,7 @@ #include <telephony.h> #include "phone.h" #include "numbertype.h" +#include "fileplayer.h" int main(int argc, char *argv[]) { @@ -63,11 +64,13 @@ int main(int argc, char *argv[]) context->setContextProperty(QStringLiteral("bindingAddress"), bindingAddress); Telephony *telephony = new Telephony(bindingAddress); context->setContextProperty("telephony", telephony); - Phone *phone = new Phone(telephony); + std::string token = secret.toStdString(); + std::string install_dir = getenv("AFM_APP_INSTALL_DIR"); + FilePlayer *player = new FilePlayer(port, token, install_dir + "/bin/Phone.wav", std::string("phone")); + Phone *phone = new Phone(telephony, player); context->setContextProperty("phone", phone); QObject::connect(telephony, &Telephony::callStateChanged, phone, &Phone::onCallStateChanged); context->setContextProperty("pbap", new Pbap(bindingAddress, context)); - std::string token = secret.toStdString(); LibHomeScreen* hs = new LibHomeScreen(); QLibWindowmanager* qwm = new QLibWindowmanager(); diff --git a/app/phone.cpp b/app/phone.cpp index 254fe24..8e8edcd 100644 --- a/app/phone.cpp +++ b/app/phone.cpp @@ -16,18 +16,14 @@ #include <QDebug> #include <QObject> -#include <QSoundEffect> #include <QTimer> #include <telephony.h> #include "phone.h" -Phone::Phone(Telephony *telephony, QObject *parent) : - QObject(parent) +Phone::Phone(Telephony *telephony, FilePlayer *player, QObject *parent) : + QObject(parent), + m_ringtone(player) { - m_ringtone.setSource(QUrl("qrc:./Phone.wav")); - m_ringtone.setVolume(0.5f); - m_ringtone.setLoopCount(QSoundEffect::Infinite); - QObject::connect(telephony, &Telephony::callStateChanged, this, &Phone::onCallStateChanged); m_call_timer.setInterval(1000); @@ -38,15 +34,18 @@ Phone::Phone(Telephony *telephony, QObject *parent) : void Phone::onCallStateChanged(QString callState) { if (callState == "disconnected") { - m_ringtone.stop(); + if (m_ringtone) + m_ringtone->stop(); m_call_timer.stop(); } else if (callState == "active") { - m_ringtone.stop(); + if (m_ringtone) + m_ringtone->stop(); m_date_time = m_date_time.fromSecsSinceEpoch(0); setElapsedTime("00:00:00"); m_call_timer.start(); } else if (callState == "incoming") { - m_ringtone.play(); + if (m_ringtone) + m_ringtone->play(true); } } diff --git a/app/phone.h b/app/phone.h index 489f1da..6cdce34 100644 --- a/app/phone.h +++ b/app/phone.h @@ -17,10 +17,10 @@ #ifndef PHONE_H #define PHONE_H -#include <QSoundEffect> #include <QTimer> #include <telephony.h> +#include "fileplayer.h" class Phone : public QObject { @@ -28,7 +28,7 @@ class Phone : public QObject Q_PROPERTY(QString elapsedTime READ elapsedTime WRITE setElapsedTime NOTIFY elapsedTimeChanged) public: - explicit Phone(Telephony *telephony, QObject *parent = Q_NULLPTR); + explicit Phone(Telephony *telephony, FilePlayer *player, QObject *parent = Q_NULLPTR); void onCallStateChanged(QString); QString elapsedTime() { return m_elapsed_time; } @@ -46,7 +46,7 @@ class Phone : public QObject private: Telephony *m_telephony; - QSoundEffect m_ringtone; + FilePlayer *m_ringtone; QTimer m_call_timer; QDateTime m_date_time; QString m_elapsed_time; diff --git a/package/config.xml b/package/config.xml index d32b0e3..893be6d 100644 --- a/package/config.xml +++ b/package/config.xml @@ -11,10 +11,12 @@ <param name="homescreen" value="ws" /> <param name="telephony" value="ws" /> <param name="bluetooth-pbap" value="ws" /> + <param name="ahl-4a" value="ws" /> </feature> <feature name="urn:AGL:widget:required-permission"> <param name="urn:AGL:permission::public:no-htdocs" value="required" /> <param name="http://tizen.org/privilege/internal/dbus" value="required" /> + <param name="urn:AGL:permission:audio:public:audiostream" value="required" /> </feature> </widget> |