aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Ranostay <matt.ranostay@konsulko.com>2017-09-27 20:39:22 -0700
committerMatt Ranostay <matt.ranostay@konsulko.com>2017-10-13 21:09:22 -0700
commit07bc96318ab8afec69cba98a09905fde69c5802e (patch)
treefac9a3373cdccb13a9d2417dc8c01dc7ec653175
parent2ed398b60a49248e94f86cbf095a4ab3b497f38c (diff)
binding: gstreamer: initial commit of gstreamer support
Add AGL gstreamer binding to control audio media independent outside of QT or respective UX interface. Bug-AGL: SPEC-931 Change-Id: Id1d0ccb1be3ab0d4111eb367d01ff2e6c4e040e0 Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt21
-rw-r--r--binding/CMakeLists.txt41
-rw-r--r--binding/afm-common.c80
-rw-r--r--binding/afm-common.h58
-rw-r--r--binding/afm-gstreamer-binding.c620
m---------conf.d/app-templates0
-rwxr-xr-xconf.d/autobuild/agl/autobuild60
-rwxr-xr-xconf.d/autobuild/linux/autobuild60
-rw-r--r--conf.d/cmake/config.cmake151
-rw-r--r--conf.d/wgt/config.xml.in23
11 files changed, 1117 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..b545da1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "conf.d/app-templates"]
+ path = conf.d/app-templates
+ url = https://gerrit.automotivelinux.org/gerrit/apps/app-templates
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..b485097
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,21 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Romain Forlot <romain.forlot@iot.bzh>
+#
+# 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.
+###########################################################################
+
+CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake)
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644
index 0000000..8b99ac0
--- /dev/null
+++ b/binding/CMakeLists.txt
@@ -0,0 +1,41 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# 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.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(afm-gstreamer-binding)
+
+ # Define project Targets
+ add_library(afm-gstreamer-binding MODULE
+ afm-gstreamer-binding.c
+ afm-common.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME} ${link_libraries})
+
+ # installation directory
+ INSTALL(TARGETS ${TARGET_NAME}
+ LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
+
diff --git a/binding/afm-common.c b/binding/afm-common.c
new file mode 100644
index 0000000..3f0f38e
--- /dev/null
+++ b/binding/afm-common.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+
+#include "afm-common.h"
+
+const char *control_commands[] = {
+ "play",
+ "pause",
+ "previous",
+ "next",
+ "seek",
+ "fast-forward",
+ "rewind",
+ "pick-track",
+ "volume",
+};
+
+int get_command_index(const char *name)
+{
+ int i;
+
+ if (name == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < NUM_CMDS; i++) {
+ if (!strcasecmp(control_commands[i], name))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+GList *find_media_index(GList *list, long int index)
+{
+ struct playlist_item *item;
+ GList *l;
+
+ for (l = list; l; l = l->next) {
+ item = l->data;
+
+ if (!item)
+ continue;
+
+ if (item->id == index)
+ return l;
+ }
+
+ return NULL;
+}
+
+void g_free_playlist_item(void *ptr)
+{
+ struct playlist_item *item = ptr;
+
+ if (ptr == NULL)
+ return;
+
+ g_free(item->title);
+ g_free(item->album);
+ g_free(item->artist);
+ g_free(item->genre);
+ g_free(item->media_path);
+ g_free(item);
+}
diff --git a/binding/afm-common.h b/binding/afm-common.h
new file mode 100644
index 0000000..8a418a1
--- /dev/null
+++ b/binding/afm-common.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * 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 _AFM_COMMON_H
+#define _AFM_COMMON_H
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include <json-c/json.h>
+
+struct playlist_item {
+ int id;
+ gchar *title;
+ gchar *album;
+ gchar *artist;
+ gchar *genre;
+ gint64 duration;
+ gchar *media_path;
+};
+
+enum {
+ PLAY_CMD = 0,
+ PAUSE_CMD,
+ PREVIOUS_CMD,
+ NEXT_CMD,
+ SEEK_CMD,
+ FASTFORWARD_CMD,
+ REWIND_CMD,
+ PICKTRACK_CMD,
+ VOLUME_CMD,
+ NUM_CMDS
+};
+
+const char *control_commands[NUM_CMDS];
+int get_command_index(const char *name);
+GList *find_media_index(GList *list, long int index);
+void g_free_playlist_item(void *ptr);
+
+#endif /* _AFM_COMMON_H */
diff --git a/binding/afm-gstreamer-binding.c b/binding/afm-gstreamer-binding.c
new file mode 100644
index 0000000..b168ccc
--- /dev/null
+++ b/binding/afm-gstreamer-binding.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include <pthread.h>
+#include <gst/gst.h>
+#include <json-c/json.h>
+#include "afm-common.h"
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+
+static struct afb_event gstreamer_event;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static GList *playlist = NULL;
+static GList *current_track = NULL;
+
+typedef struct _CustomData {
+ GstElement *playbin;
+ gboolean playing;
+ guint volume;
+ gint64 position;
+ gint64 duration;
+} CustomData;
+
+CustomData data = {
+ .volume = 50,
+ .position = GST_CLOCK_TIME_NONE,
+ .duration = GST_CLOCK_TIME_NONE,
+};
+
+static json_object *populate_json(struct playlist_item *track)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *jstring = json_object_new_string(track->media_path);
+ json_object_object_add(jresp, "path", jstring);
+
+ if (track->title) {
+ jstring = json_object_new_string(track->title);
+ json_object_object_add(jresp, "title", jstring);
+ }
+
+ if (track->album) {
+ jstring = json_object_new_string(track->album);
+ json_object_object_add(jresp, "album", jstring);
+ }
+
+ if (track->artist) {
+ jstring = json_object_new_string(track->artist);
+ json_object_object_add(jresp, "artist", jstring);
+ }
+
+ if (track->genre) {
+ jstring = json_object_new_string(track->genre);
+ json_object_object_add(jresp, "genre", jstring);
+ }
+
+ if (track->duration > 0)
+ json_object_object_add(jresp, "duration",
+ json_object_new_int64(track->duration));
+
+ json_object_object_add(jresp, "index",
+ json_object_new_int(track->id));
+
+ return jresp;
+}
+
+static gboolean populate_from_json(struct playlist_item *item, json_object *jdict)
+{
+ gboolean ret;
+ json_object *val = NULL;
+
+ ret = json_object_object_get_ex(jdict, "path", &val);
+ if (!ret)
+ return ret;
+ item->media_path = g_strdup(json_object_get_string(val));
+
+ ret = json_object_object_get_ex(jdict, "title", &val);
+ if (ret) {
+ item->title = g_strdup(json_object_get_string(val));
+ }
+
+ ret = json_object_object_get_ex(jdict, "album", &val);
+ if (ret) {
+ item->album = g_strdup(json_object_get_string(val));
+ }
+
+ ret = json_object_object_get_ex(jdict, "artist", &val);
+ if (ret) {
+ item->artist = g_strdup(json_object_get_string(val));
+ }
+
+ ret = json_object_object_get_ex(jdict, "genre", &val);
+ if (ret) {
+ item->genre = g_strdup(json_object_get_string(val));
+ }
+
+ ret = json_object_object_get_ex(jdict, "duration", &val);
+ if (ret) {
+ item->duration = json_object_get_int64(val);
+ }
+
+ return TRUE;
+}
+
+static int set_media_uri(struct playlist_item *item)
+{
+ if (!item || !item->media_path)
+ return -ENOENT;
+
+ gst_element_set_state(data.playbin, GST_STATE_NULL);
+
+ g_object_set(data.playbin, "uri", item->media_path, NULL);
+
+ data.position = GST_CLOCK_TIME_NONE;
+ data.duration = GST_CLOCK_TIME_NONE;
+
+ if (data.playing)
+ gst_element_set_state(data.playbin, GST_STATE_PLAYING);
+
+ g_object_set(data.playbin, "volume", data.volume / 100.0, NULL);
+
+ return 0;
+}
+
+static void populate_playlist(json_object *jquery)
+{
+ int i, idx = 0;
+ GList *list = g_list_last(playlist);
+
+ if (list && list->data) {
+ struct playlist_item *item = list->data;
+ idx = item->id + 1;
+ }
+
+ for (i = 0; i < json_object_array_length(jquery); i++) {
+ json_object *jdict = json_object_array_get_idx(jquery, i);
+ struct playlist_item *item = g_malloc0(sizeof(*item));
+ int ret;
+
+ if (item == NULL)
+ break;
+
+ ret = populate_from_json(item, jdict);
+ if (!ret) {
+ g_free_playlist_item(item);
+ continue;
+ }
+
+ item->id = idx++;
+ playlist = g_list_append(playlist, item);
+ }
+
+ current_track = g_list_first(playlist);
+ set_media_uri(current_track->data);
+}
+
+static void audio_playlist(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "list");
+ json_object *jresp = NULL;
+
+ pthread_mutex_lock(&mutex);
+
+ if (value) {
+ json_object *jquery;
+
+ if (playlist) {
+ g_list_free_full(playlist, g_free_playlist_item);
+ playlist = NULL;
+ }
+
+ jquery = json_tokener_parse(value);
+ populate_playlist(jquery);
+
+ if (playlist == NULL)
+ afb_req_fail(request, "failed", "invalid playlist");
+ else
+ afb_req_success(request, NULL, NULL);
+
+ json_object_put(jquery);
+ } else {
+ GList *l;
+ json_object *jarray = json_object_new_array();
+ jresp = json_object_new_object();
+
+ for (l = playlist; l; l = l->next) {
+ json_object *item = populate_json(l->data);
+ json_object_array_add(jarray, item);
+ }
+
+ json_object_object_add(jresp, "list", jarray);
+ afb_req_success(request, jresp, "Playlist results");
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static int seek_track(int cmd)
+{
+ GList *item = (cmd == NEXT_CMD) ? current_track->next : current_track->prev;
+ int ret;
+
+ if (item == NULL)
+ return -EINVAL;
+
+ ret = set_media_uri(item->data);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (data.playing)
+ gst_element_set_state(data.playbin, GST_STATE_PLAYING);
+
+ current_track = item;
+
+ return 0;
+}
+
+static int seek_stream(const char *value, int cmd)
+{
+ gint64 position, current = 0;
+
+ if (value == NULL)
+ return -EINVAL;
+
+ position = strtoll(value, NULL, 10);
+
+ if (cmd != SEEK_CMD) {
+ gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current);
+ position = (current / GST_MSECOND) + (FASTFORWARD_CMD ? position : -position);
+ }
+
+ if (position < 0)
+ position = 0;
+
+ if (data.duration > 0 && position > data.duration)
+ position = data.duration;
+
+ return gst_element_seek_simple(data.playbin, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+ position * GST_MSECOND);
+}
+
+/* @value can be one of the following values:
+ * play - go to playing transition
+ * pause - go to pause transition
+ * previous - skip to previous track
+ * next - skip to the next track
+ * seek - go to position (in milliseconds)
+ *
+ * fast-forward - skip forward in milliseconds
+ * rewind - skip backward in milliseconds
+ *
+ * pick-track - select track via index number
+ * volume - set volume between 0 - 100%
+ */
+
+static void controls(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+ const char *position = afb_req_value(request, "position");
+ int cmd = get_command_index(value);
+
+ if (!value) {
+ afb_req_fail(request, "failed", "no value was passed");
+ return;
+ }
+
+ pthread_mutex_lock(&mutex);
+ errno = 0;
+
+ switch (cmd) {
+ case PLAY_CMD:
+ gst_element_set_state(data.playbin, GST_STATE_PLAYING);
+ data.playing = TRUE;
+ break;
+ case PAUSE_CMD:
+ gst_element_set_state(data.playbin, GST_STATE_PAUSED);
+ data.playing = FALSE;
+ break;
+ case PREVIOUS_CMD:
+ case NEXT_CMD:
+ seek_track(cmd);
+ break;
+ case SEEK_CMD:
+ case FASTFORWARD_CMD:
+ case REWIND_CMD:
+ seek_stream(position, cmd);
+ break;
+ case PICKTRACK_CMD: {
+ const char *parameter = afb_req_value(request, "index");
+ long int idx = strtol(parameter, NULL, 10);
+ GList *list = NULL;
+
+ if (idx == 0 && !errno) {
+ afb_req_fail(request, "failed", "invalid index");
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ list = find_media_index(playlist, idx);
+ if (list != NULL) {
+ struct playlist_item *item = list->data;
+ set_media_uri(item);
+ current_track = list;
+ } else {
+ afb_req_fail(request, "failed", "couldn't find index");
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ break;
+ }
+ case VOLUME_CMD: {
+ const char *parameter = afb_req_value(request, "volume");
+ long int volume = strtol(parameter, NULL, 10);
+
+ if (volume == 0 && !errno) {
+ afb_req_fail(request, "failed", "invalid volume");
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ if (volume < 0)
+ volume = 0;
+ if (volume > 100)
+ volume = 100;
+
+ g_object_set(data.playbin, "volume", volume / 100.0, NULL);
+
+ break;
+ }
+ default:
+ afb_req_fail(request, "failed", "unknown command");
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ afb_req_success(request, NULL, NULL);
+ pthread_mutex_unlock(&mutex);
+}
+
+static void metadata(struct afb_req request)
+{
+ struct playlist_item *track;
+ json_object *jresp;
+
+ pthread_mutex_lock(&mutex);
+
+ if (current_track == NULL || current_track->data == NULL) {
+ afb_req_fail(request, "failed", "No playlist");
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ track = current_track->data;
+ jresp = populate_json(track);
+
+ if (data.duration != GST_CLOCK_TIME_NONE)
+ json_object_object_add(jresp, "duration",
+ json_object_new_int64(data.duration / GST_MSECOND));
+
+ if (data.position != GST_CLOCK_TIME_NONE)
+ json_object_object_add(jresp, "position",
+ json_object_new_int64(data.position / GST_MSECOND));
+
+ json_object_object_add(jresp, "volume",
+ json_object_new_int(data.volume));
+
+ pthread_mutex_unlock(&mutex);
+
+ afb_req_success(request, jresp, "Metadata results");
+}
+
+static void subscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+
+ if (value && !strcasecmp(value, "gstreamer")) {
+ afb_req_subscribe(request, gstreamer_event);
+ afb_req_success(request, NULL, NULL);
+ return;
+ }
+
+ afb_req_fail(request, "failed", "Invalid event");
+}
+
+static void unsubscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+
+ if (value && !strcasecmp(value, "gstreamer")) {
+ afb_req_unsubscribe(request, gstreamer_event);
+ afb_req_success(request, NULL, NULL);
+ return;
+ }
+
+ afb_req_fail(request, "failed", "Invalid event");
+}
+
+static gboolean handle_message(GstBus *bus, GstMessage *msg, CustomData *data)
+{
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS: {
+ int ret;
+
+ pthread_mutex_lock(&mutex);
+
+ data->position = GST_CLOCK_TIME_NONE;
+ data->duration = GST_CLOCK_TIME_NONE;
+
+ ret = seek_track(NEXT_CMD);
+ if (ret < 0) {
+ data->playing = FALSE;
+ current_track = playlist;
+ } else if (data->playing) {
+ gst_element_set_state(data->playbin, GST_STATE_PLAYING);
+ }
+
+ pthread_mutex_unlock(&mutex);
+ break;
+ }
+ case GST_MESSAGE_DURATION:
+ data->duration = GST_CLOCK_TIME_NONE;
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean position_event(CustomData *data)
+{
+ struct playlist_item *track;
+ json_object *jresp = NULL;
+
+ pthread_mutex_lock(&mutex);
+
+ if (!data->playing || current_track == NULL) {
+ pthread_mutex_unlock(&mutex);
+ return TRUE;
+ }
+
+ track = current_track->data;
+ jresp = populate_json(track);
+
+ if (!GST_CLOCK_TIME_IS_VALID(data->duration))
+ gst_element_query_duration(data->playbin,
+ GST_FORMAT_TIME, &data->duration);
+
+ gst_element_query_position(data->playbin,
+ GST_FORMAT_TIME, &data->position);
+
+ json_object_object_add(jresp, "duration",
+ json_object_new_int64(data->duration / GST_MSECOND));
+ json_object_object_add(jresp, "position",
+ json_object_new_int64(data->position / GST_MSECOND));
+
+ pthread_mutex_unlock(&mutex);
+
+ afb_event_push(gstreamer_event, jresp);
+
+ return TRUE;
+}
+
+static void *gstreamer_loop_thread(void *ptr)
+{
+ GstBus *bus;
+ json_object *query, *response;
+ int ret;
+
+ gst_init(NULL, NULL);
+
+ data.playbin = gst_element_factory_make("playbin", "playbin");
+ if (!data.playbin) {
+ AFB_ERROR("Cannot create playbin");
+ exit(1);
+ }
+
+ bus = gst_element_get_bus(data.playbin);
+ gst_bus_add_watch(bus, (GstBusFunc) handle_message, &data);
+ g_timeout_add_seconds(1, (GSourceFunc) position_event, &data);
+
+ ret = afb_service_call_sync("mediascanner", "media_result", NULL, &response);
+ if (!ret) {
+ json_object *query = json_object_object_get(response, "response");
+
+ if (query)
+ query = json_object_object_get(query, "Media");
+
+ if (query)
+ populate_playlist(query);
+ }
+ json_object_put(response);
+
+ g_main_loop_run(g_main_loop_new(NULL, FALSE));
+
+ return NULL;
+}
+
+static void onevent(const char *event, struct json_object *object)
+{
+ if (!g_strcmp0(event, "mediascanner/media_added")) {
+ pthread_mutex_lock(&mutex);
+
+ json_object *query = json_object_object_get(object, "Media");
+ if (query)
+ populate_playlist(query);
+
+ pthread_mutex_unlock(&mutex);
+ } else if (!g_strcmp0(event, "mediascanner/media_removed")) {
+ json_object *query = json_object_object_get(object, "Path");
+ const char *path = json_object_get_string(query);
+ GList *l = playlist;
+
+ pthread_mutex_lock(&mutex);
+
+ while (l) {
+ struct playlist_item *item = l->data;
+
+ l = l->next;
+
+ if (!strncasecmp(path, item->media_path, strlen(path))) {
+ playlist = g_list_remove(playlist, item);
+ g_free_playlist_item(item);
+
+ if (current_track->data == item) {
+ current_track = NULL;
+ gst_element_set_state(data.playbin, GST_STATE_NULL);
+ }
+ }
+ }
+
+ current_track = g_list_first(playlist);
+
+ pthread_mutex_unlock(&mutex);
+ } else {
+ AFB_ERROR("Invalid event: %s", event);
+ }
+}
+
+static int init() {
+ pthread_t thread_id;
+ json_object *response, *query;
+ int ret;
+
+ ret = afb_daemon_require_api("mediascanner", 1);
+ if (ret < 0) {
+ AFB_ERROR("Cannot request mediascanner");
+ return ret;
+ }
+
+ query = json_object_new_object();
+ json_object_object_add(query, "value", json_object_new_string("media_added"));
+
+ ret = afb_service_call_sync("mediascanner", "subscribe", query, &response);
+ json_object_put(response);
+
+ if (ret < 0) {
+ AFB_ERROR("Cannot subscribe to mediascanner media_added event");
+ return ret;
+ }
+
+ query = json_object_new_object();
+ json_object_object_add(query, "value", json_object_new_string("media_removed"));
+
+ ret = afb_service_call_sync("mediascanner", "subscribe", query, &response);
+ json_object_put(response);
+
+ if (ret < 0) {
+ AFB_ERROR("Cannot subscribe to mediascanner media_remove event");
+ return ret;
+ }
+
+ gstreamer_event = afb_daemon_make_event("gstreamer");
+
+ return pthread_create(&thread_id, NULL, gstreamer_loop_thread, NULL);
+}
+
+static const struct afb_verb_v2 binding_verbs[] = {
+ { .verb = "playlist", .callback = audio_playlist, .info = "Get/set playlist" },
+ { .verb = "controls", .callback = controls, .info = "Audio controls" },
+ { .verb = "metadata", .callback = metadata, .info = "Get metadata of current track" },
+ { .verb = "subscribe", .callback = subscribe, .info = "Subscribe to GStreamer events" },
+ { .verb = "unsubscribe", .callback = unsubscribe, .info = "Unsubscribe to GStreamer events" },
+ { }
+};
+
+/*
+ * binder API description
+ */
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "gstreamer",
+ .specification = "GStreamer API",
+ .verbs = binding_verbs,
+ .onevent = onevent,
+ .init = init,
+};
diff --git a/conf.d/app-templates b/conf.d/app-templates
new file mode 160000
+Subproject 8967162dd12bce89f9ae27f5c9bce7b78624e3f
diff --git a/conf.d/autobuild/agl/autobuild b/conf.d/autobuild/agl/autobuild
new file mode 100755
index 0000000..759f6be
--- /dev/null
+++ b/conf.d/autobuild/agl/autobuild
@@ -0,0 +1,60 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015, 2016 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build)
+DEST := ${BUILD_DIR}/target
+
+.PHONY: all clean distclean configure build package help
+
+all: help
+
+help:
+ @echo "List of targets available:"
+ @echo ""
+ @echo "- all"
+ @echo "- clean"
+ @echo "- distclean"
+ @echo "- configure"
+ @echo "- build"
+ @echo "- package"
+ @echo ""
+ @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt"
+ @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+clean:
+ @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean
+
+distclean:
+ @rm -rf ${BUILD_DIR}
+
+configure: ${BUILD_DIR}/Makefile
+
+build: configure
+ @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+ @mkdir -p ${BUILD_DIR}/$@/bin
+ @mkdir -p ${BUILD_DIR}/$@/etc
+ @mkdir -p ${BUILD_DIR}/$@/lib
+ @mkdir -p ${BUILD_DIR}/$@/htdocs
+ @mkdir -p ${BUILD_DIR}/$@/data
+ @cmake --build ${BUILD_DIR} --target widget
+ @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST}
+
+${BUILD_DIR}/Makefile:
+ @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+ @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
diff --git a/conf.d/autobuild/linux/autobuild b/conf.d/autobuild/linux/autobuild
new file mode 100755
index 0000000..759f6be
--- /dev/null
+++ b/conf.d/autobuild/linux/autobuild
@@ -0,0 +1,60 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015, 2016 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build)
+DEST := ${BUILD_DIR}/target
+
+.PHONY: all clean distclean configure build package help
+
+all: help
+
+help:
+ @echo "List of targets available:"
+ @echo ""
+ @echo "- all"
+ @echo "- clean"
+ @echo "- distclean"
+ @echo "- configure"
+ @echo "- build"
+ @echo "- package"
+ @echo ""
+ @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt"
+ @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+clean:
+ @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean
+
+distclean:
+ @rm -rf ${BUILD_DIR}
+
+configure: ${BUILD_DIR}/Makefile
+
+build: configure
+ @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+ @mkdir -p ${BUILD_DIR}/$@/bin
+ @mkdir -p ${BUILD_DIR}/$@/etc
+ @mkdir -p ${BUILD_DIR}/$@/lib
+ @mkdir -p ${BUILD_DIR}/$@/htdocs
+ @mkdir -p ${BUILD_DIR}/$@/data
+ @cmake --build ${BUILD_DIR} --target widget
+ @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST}
+
+${BUILD_DIR}/Makefile:
+ @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+ @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
new file mode 100644
index 0000000..234cc76
--- /dev/null
+++ b/conf.d/cmake/config.cmake
@@ -0,0 +1,151 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+#
+# 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.
+###########################################################################
+
+# Project Info
+# ------------------
+set(PROJECT_NAME agl-service-gstreamer)
+set(PROJECT_PRETTY_NAME "AFM binding for GStreamer")
+set(PROJECT_DESCRIPTION "Binding for GStreamer media control")
+set(PROJECT_VERSION "1.0")
+set(PROJECT_ICON "icon.png")
+set(PROJECT_LICENSE "APL2.0")
+set(PROJECT_LANGUAGES,"C")
+
+# Where are stored default templates files from submodule or subtree app-templates in your project tree
+# relative to the root project directory
+set(PROJECT_APP_TEMPLATES_DIR "conf.d/app-templates")
+
+# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain
+# but used and must be built and linked.
+# set(PROJECT_LIBDIR "libs")
+
+# Where are stored data for your application. Pictures, static resources must be placed in that folder.
+# set(PROJECT_RESOURCES "data")
+
+# Which directories inspect to find CMakeLists.txt target files
+# set(PROJECT_SRC_DIR_PATTERN "*")
+
+# Compilation Mode (DEBUG, RELEASE)
+# ----------------------------------
+set(CMAKE_BUILD_TYPE "DEBUG")
+
+# Kernel selection if needed. You can choose between a
+# mandatory version to impose a minimal version.
+# Or check Kernel minimal version and just print a Warning
+# about missing features and define a preprocessor variable
+# to be used as preprocessor condition in code to disable
+# incompatibles features. Preprocessor define is named
+# KERNEL_MINIMAL_VERSION_OK.
+#
+# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and
+# Yocto SDK Kernel version.
+# -----------------------------------------------
+#set(kernel_mandatory_version 4.8)
+
+# Compiler selection if needed. Impose a minimal version.
+# -----------------------------------------------
+set (gcc_minimal_version 4.9)
+
+# PKG_CONFIG required packages
+# -----------------------------
+set (PKG_REQUIRED_LIST
+ json-c
+ gstreamer-1.0
+ glib-2.0
+ gobject-2.0
+ libsystemd>=222
+ afb-daemon
+)
+
+# Customize link option
+# -----------------------------
+list (APPEND link_libraries -pthread)
+
+# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable]
+# ---------------------------------------------------------------------
+set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt)
+set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig)
+set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib)
+
+# Optional location for config.xml.in
+# -----------------------------------
+set(WIDGET_CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in)
+
+# Mandatory widget Mimetype specification of the main unit
+# --------------------------------------------------------------------------
+# Choose between :
+#- text/html : HTML application,
+# content.src designates the home page of the application
+#
+#- application/vnd.agl.native : AGL compatible native,
+# content.src designates the relative path of the binary.
+#
+# - application/vnd.agl.service: AGL service, content.src is not used.
+#
+#- ***application/x-executable***: Native application,
+# content.src designates the relative path of the binary.
+# For such application, only security setup is made.
+#
+set(WIDGET_TYPE application/vnd.agl.service)
+
+# Mandatory Widget entry point file of the main unit
+# --------------------------------------------------------------
+# This is the file that will be executed, loaded,
+# at launch time by the application framework.
+#
+set(WIDGET_ENTRY_POINT lib/libafm-gstreamer-binding.so)
+
+# Print a helper message when every thing is finished
+# ----------------------------------------------------
+set(CLOSING_MESSAGE "Test with: afb-daemon --rootdir=\$\$(pwd)/package --binding=\$\$(pwd)/package/${WIDGET_ENTRY_POINT} --port=1234 --tracereq=common --token=\"1\" --verbose")
+set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt")
+
+# Optional dependencies order
+# ---------------------------
+#set(EXTRA_DEPENDENCIES_ORDER)
+
+# Optional Extra global include path
+# -----------------------------------
+#set(EXTRA_INCLUDE_DIRS)
+
+# Optional extra libraries
+# -------------------------
+#set(EXTRA_LINK_LIBRARIES)
+
+# Optional force binding installation
+# ------------------------------------
+# set(BINDINGS_INSTALL_PREFIX PrefixPath )
+
+# Optional force binding Linking flag
+# ------------------------------------
+# set(BINDINGS_LINK_FLAG LinkOptions )
+
+# Optional force package prefix generation, like widget
+# -----------------------------------------------------
+# set(PKG_PREFIX DestinationPath)
+
+# Optional Application Framework security token
+# and port use for remote debugging.
+#------------------------------------------------------------
+#set(AFB_TOKEN "" CACHE PATH "Default AFB_TOKEN")
+#set(AFB_REMPORT "1234" CACHE PATH "Default AFB_TOKEN")
+
+# This include is mandatory and MUST happens at the end
+# of this file, else you expose you to unexpected behavior
+# -----------------------------------------------------------
+include(${PROJECT_APP_TEMPLATES_DIR}/cmake/common.cmake)
diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in
new file mode 100644
index 0000000..b1f9ac8
--- /dev/null
+++ b/conf.d/wgt/config.xml.in
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@">
+ <name>@PROJECT_NAME@</name>
+ <icon src="@PROJECT_ICON@"/>
+ <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/>
+ <description>@PROJECT_DESCRIPTION@</description>
+ <author>@PROJECT_AUTHOR@ &lt;@PROJECT_AUTHOR_MAIL@&gt;</author>
+ <license>@PROJECT_LICENSE@</license>
+
+ <feature name="urn:AGL:widget:required-permission">
+ <param name="urn:AGL:permission::public:hidden" value="required" />
+ <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+ </feature>
+
+ <feature name="urn:AGL:widget:provided-api">
+ <param name="gstreamer" value="ws" />
+ </feature>
+
+ <feature name="urn:AGL:widget:required-api">
+ <param name="mediascanner" value="ws" />
+ <param name="@WIDGET_ENTRY_POINT@" value="local" />
+ </feature>
+</widget>