summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/Dialer.qml1
-rw-r--r--app/app.pri6
-rw-r--r--app/app.pro11
-rw-r--r--app/fileplayer.cpp290
-rw-r--r--app/fileplayer.h62
-rw-r--r--app/main.cpp7
-rw-r--r--app/phone.cpp19
-rw-r--r--app/phone.h6
-rw-r--r--package/config.xml2
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>