summaryrefslogtreecommitdiffstats
path: root/binding/afm-gstreamer-binding.c
diff options
context:
space:
mode:
Diffstat (limited to 'binding/afm-gstreamer-binding.c')
-rw-r--r--binding/afm-gstreamer-binding.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/binding/afm-gstreamer-binding.c b/binding/afm-gstreamer-binding.c
new file mode 100644
index 0000000..948e00a
--- /dev/null
+++ b/binding/afm-gstreamer-binding.c
@@ -0,0 +1,379 @@
+/*
+ * 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 <stdbool.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;
+ gboolean play_next;
+ gboolean seek_enabled;
+ gboolean seek_done;
+ gint64 position;
+ gint64 duration;
+} CustomData;
+
+CustomData data = {};
+
+static void g_free_playlist_item(void *ptr)
+{
+ struct playlist_item *item = ptr;
+
+ g_free(item->title);
+ g_free(item->album);
+ g_free(item->artist);
+ g_free(item->media_path);
+ g_free(item);
+}
+
+
+static json_object *populate_json(struct playlist_item *track)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *jstring = json_object_new_string(track->title);
+
+ json_object_object_add(jresp, "title", jstring);
+
+ jstring = json_object_new_string(track->album);
+ json_object_object_add(jresp, "album", jstring);
+
+ jstring = json_object_new_string(track->artist);
+ json_object_object_add(jresp, "artist", jstring);
+
+ jstring = json_object_new_string(track->media_path);
+ json_object_object_add(jresp, "media-path", jstring);
+
+ json_object_object_add(jresp, "duration",
+ json_object_new_int(track->duration));
+
+ json_object_object_add(jresp, "index",
+ json_object_new_int(track->id));
+
+ return jresp;
+}
+
+/*
+ * list parameter is a list of media paths
+ */
+
+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) {
+ if (playlist) {
+ g_list_free_full(playlist, g_free_playlist_item);
+ playlist = NULL;
+ }
+
+ jresp = json_tokener_parse(value);
+ /* TODO */
+ json_object_put(jresp);
+ } else {
+ jresp = json_object_new_object();
+ /* TODO */
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static int set_media_uri(struct playlist_item *item)
+{
+ if (item || item->media_path)
+ return -ENOENT;
+
+ g_object_set(data.playbin, "uri", item->media_path, NULL);
+
+ 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 + (FASTFORWARD_CMD ? position : -position);
+ }
+
+ if (position < 0)
+ position = 0;
+
+ 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
+ */
+
+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, ret = 0;
+
+ if (!value) {
+ afb_req_fail(request, "failed", "no value was passed");
+ return;
+ }
+
+ cmd = get_command_index(value);
+
+ pthread_mutex_lock(&mutex);
+ errno = 0;
+
+ switch (cmd) {
+ case PLAY_CMD:
+ gst_element_set_state(data.playbin, GST_STATE_PLAYING);
+ break;
+ case PAUSE_CMD:
+ gst_element_set_state(data.playbin, GST_STATE_PAUSED);
+ break;
+ case PREVIOUS_CMD: {
+ GList *item = current_track->prev;
+
+ ret = set_media_uri(item->data);
+ if (ret < 0 || item)
+ break;
+ current_track = item;
+ break;
+ }
+ case NEXT_CMD: {
+ GList *item = current_track->prev;
+
+ ret = set_media_uri(item->data);
+ if (ret < 0 || item)
+ break;
+ current_track = item;
+ 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)
+ break;
+
+ list = find_media_index(playlist, idx);
+ if (list != NULL) {
+ struct playlist_item *item = list->data;
+ set_media_uri(item);
+ current_track = list;
+ }
+
+ 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);
+
+ json_object_object_add(jresp, "position",
+ json_object_new_int64(data.position));
+
+ 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 bool handle_message(GstBus *bus, GstMessage *msg, CustomData *data)
+{
+ /*TODO*/
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_STREAM_STATUS:
+ case GST_MESSAGE_EOS:
+ case GST_MESSAGE_PROGRESS:
+ case GST_MESSAGE_DURATION_CHANGED:
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static void *gstreamer_loop_thread(void *ptr)
+{
+ GstBus *bus;
+
+ data.duration = GST_CLOCK_TIME_NONE;
+
+ 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_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")) {
+ /* TODO */
+ } else if (g_strcmp0(event, "mediascanner/media_removed")) {
+ /* TODO */
+ } 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("mediaplayer", "subscribe", query, &response);
+ json_object_put(response);
+
+ if (ret < 0) {
+ AFB_ERROR("Cannot subscribe to gstreamer service");
+ return ret;
+ }
+
+ 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,
+};