diff --git a/Makefile b/Makefile
index 2f1d995e..d93c70bb 100644
--- a/Makefile
+++ b/Makefile
@@ -178,6 +178,7 @@ SRCS-$(CONFIG_MPEGTS) += \
src/input/mpegts/dvb_charset.c \
src/input/mpegts/dvb_psi.c \
src/input/mpegts/tsdemux.c \
+ src/input/mpegts/mpegts_mux_sched.c \
# MPEGTS DVB
SRCS-${CONFIG_MPEGTS_DVB} += \
diff --git a/src/api/api_mpegts.c b/src/api/api_mpegts.c
index 9409319e..c5084aa1 100644
--- a/src/api/api_mpegts.c
+++ b/src/api/api_mpegts.c
@@ -251,6 +251,43 @@ api_mpegts_service_grid
}
}
+/*
+ * Mux scheduler
+ */
+static void
+api_mpegts_mux_sched_grid
+ ( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
+{
+ mpegts_mux_sched_t *mms;
+ LIST_FOREACH(mms, &mpegts_mux_sched_all, mms_link)
+ idnode_set_add(ins, (idnode_t*)mms, &conf->filter);
+}
+
+static int
+api_mpegts_mux_sched_create
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ int err;
+ htsmsg_t *conf;
+ mpegts_mux_sched_t *mms;
+
+ if (!(conf = htsmsg_get_map(args, "conf")))
+ return EINVAL;
+
+ pthread_mutex_lock(&global_lock);
+ mms = mpegts_mux_sched_create(NULL, conf);
+ if (mms) {
+ err = 0;
+ *resp = htsmsg_create_map();
+ mpegts_mux_sched_save(mms);
+ } else {
+ err = EINVAL;
+ }
+ pthread_mutex_unlock(&global_lock);
+
+ return err;
+}
+
#if ENABLE_MPEGTS_DVB
static int
api_dvb_scanfile_list
@@ -321,6 +358,9 @@ api_mpegts_init ( void )
{ "mpegts/mux/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&mpegts_mux_class },
{ "mpegts/service/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_mpegts_service_grid },
{ "mpegts/service/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&mpegts_service_class },
+ { "mpegts/mux_sched/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&mpegts_mux_sched_class },
+ { "mpegts/mux_sched/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_mpegts_mux_sched_grid },
+ { "mpegts/mux_sched/create", ACCESS_ANONYMOUS, api_mpegts_mux_sched_create, NULL },
#if ENABLE_MPEGTS_DVB
{ "dvb/scanfile/list", ACCESS_ANONYMOUS, api_dvb_scanfile_list, NULL },
#endif
diff --git a/src/input.h b/src/input.h
index 2b28884d..6de228f9 100644
--- a/src/input.h
+++ b/src/input.h
@@ -115,6 +115,7 @@ void tvh_input_stream_destroy ( tvh_input_stream_t *st );
#if ENABLE_MPEGTS
#include "input/mpegts.h"
+#include "input/mpegts/mpegts_mux_sched.h"
#if ENABLE_MPEGTS_DVB
#include "input/mpegts/mpegts_dvb.h"
#endif
diff --git a/src/input/mpegts/mpegts_mux_sched.c b/src/input/mpegts/mpegts_mux_sched.c
new file mode 100644
index 00000000..13e6762b
--- /dev/null
+++ b/src/input/mpegts/mpegts_mux_sched.c
@@ -0,0 +1,342 @@
+/*
+ * Tvheadend - TS file input system
+ *
+ * Copyright (C) 2014 Adam Sutton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "tvheadend.h"
+#include "input.h"
+#include "input/mpegts/mpegts_mux_sched.h"
+#include "streaming.h"
+#include "settings.h"
+
+static void mpegts_mux_sched_timer ( void *p );
+static void mpegts_mux_sched_input ( void *p, streaming_message_t *sm );
+
+mpegts_mux_sched_list_t mpegts_mux_sched_all;
+
+/******************************************************************************
+ * Class
+ *****************************************************************************/
+
+static void
+mpegts_mux_sched_set_timer ( mpegts_mux_sched_t *mms )
+{
+ /* Upate timer */
+ if (!mms->mms_enabled) {
+ if (mms->mms_sub)
+ subscription_unsubscribe(mms->mms_sub);
+ mms->mms_sub = NULL;
+ mms->mms_active = 0;
+ gtimer_disarm(&mms->mms_timer);
+ } else if (mms->mms_active) {
+ if (mms->mms_timeout <= 0)
+ gtimer_disarm(&mms->mms_timer);
+ else {
+ gtimer_arm(&mms->mms_timer, mpegts_mux_sched_timer, mms,
+ mms->mms_timeout);
+ }
+ } else {
+ time_t now, nxt;
+ time(&now);
+ if (!cron_next(&mms->mms_cronjob, now, &nxt)) {
+ gtimer_arm_abs(&mms->mms_timer, mpegts_mux_sched_timer, mms, nxt);
+ }
+ }
+}
+
+static void
+mpegts_mux_sched_class_save ( idnode_t *in )
+{
+ mpegts_mux_sched_t *mms = (mpegts_mux_sched_t*)in;
+
+ /* Update timer */
+ mpegts_mux_sched_set_timer(mms);
+
+ /* Save */
+ mpegts_mux_sched_save(mms);
+}
+
+static void
+mpegts_mux_sched_class_delete ( idnode_t *in )
+{
+ mpegts_mux_sched_delete((mpegts_mux_sched_t*)in, 1);
+}
+
+static htsmsg_t *
+mpegts_mux_sched_class_mux_list ( void *o )
+{
+ htsmsg_t *m, *p;
+
+ p = htsmsg_create_map();
+ htsmsg_add_str (p, "class", "mpegts_mux");
+ htsmsg_add_bool(p, "enum", 1);
+
+ m = htsmsg_create_map();
+ htsmsg_add_str (m, "type", "api");
+ htsmsg_add_str (m, "uri", "idnode/load");
+ htsmsg_add_str (m, "event", "mpegts_mux");
+ htsmsg_add_msg (m, "params", p);
+
+ return m;
+}
+
+static int
+mpegts_mux_sched_class_cron_set ( void *p, const void *v )
+{
+ mpegts_mux_sched_t *mms = p;
+ const char *str = v;
+ if (strcmp(str, mms->mms_cronstr ?: "")) {
+ if (!cron_set(&mms->mms_cronjob, str)) {
+ free(mms->mms_cronstr);
+ mms->mms_cronstr = strdup(str);
+ return 1;
+ } else {
+ tvhwarn("muxsched", "invalid cronjob spec (%s)", str);
+ }
+ }
+ return 0;
+}
+
+const idclass_t mpegts_mux_sched_class =
+{
+ .ic_class = "mpegts_mux_sched",
+ .ic_caption = "Mux Sched Entry",
+ .ic_event = "mpegts_mux_sched",
+ .ic_save = mpegts_mux_sched_class_save,
+ .ic_delete = mpegts_mux_sched_class_delete,
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = "Enabled",
+ .off = offsetof(mpegts_mux_sched_t, mms_enabled),
+ .def.i = 1,
+ },
+ {
+ .type = PT_STR,
+ .id = "mux",
+ .name = "Mux",
+ .off = offsetof(mpegts_mux_sched_t, mms_mux),
+ .list = mpegts_mux_sched_class_mux_list,
+ },
+ {
+ .type = PT_STR,
+ .id = "cron",
+ .name = "Cron",
+ .off = offsetof(mpegts_mux_sched_t, mms_cronstr),
+ .set = mpegts_mux_sched_class_cron_set,
+ },
+ {
+ .type = PT_INT,
+ .id = "timeout",
+ .name = "Timout (secs)",
+ .off = offsetof(mpegts_mux_sched_t, mms_timeout),
+ },
+ {
+ },
+ }
+};
+
+/******************************************************************************
+ * Input
+ *****************************************************************************/
+
+static void
+mpegts_mux_sched_input ( void *p, streaming_message_t *sm )
+{
+ mpegts_mux_sched_t *mms = p;
+
+ switch (sm->sm_type) {
+ case SMT_STOP:
+ gtimer_arm(&mms->mms_timer, mpegts_mux_sched_timer, mms, 0);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ streaming_msg_free(sm);
+}
+
+/******************************************************************************
+ * Timer
+ *****************************************************************************/
+
+static void
+mpegts_mux_sched_timer ( void *p )
+{
+ mpegts_mux_t *mm;
+ mpegts_mux_sched_t *mms = p;
+ time_t now, nxt;
+
+ /* Not enabled (shouldn't be running) */
+ if (!mms->mms_enabled)
+ return;
+
+ /* Invalid config (creating?) */
+ if (!mms->mms_mux)
+ return;
+
+ /* Find mux */
+ if (!(mm = mpegts_mux_find(mms->mms_mux))) {
+ tvhdebug("muxsched", "mux has been removed, delete sched entry");
+ mpegts_mux_sched_delete(mms, 1);
+ return;
+ }
+
+ /* Current time */
+ time(&now);
+
+ /* Start sub */
+ if (!mms->mms_active) {
+ assert(mms->mms_sub == NULL);
+
+ mms->mms_sub
+ = subscription_create_from_mux(mm, mms->mms_weight,
+ mms->mms_creator ?: "",
+ &mms->mms_input,
+ SUBSCRIPTION_NONE,
+ NULL, NULL, NULL, NULL);
+
+ /* Failed (try-again soon) */
+ if (!mms->mms_sub) {
+ gtimer_arm(&mms->mms_timer, mpegts_mux_sched_timer, mms, 60);
+
+ /* OK */
+ } else {
+ mms->mms_active = 1;
+ if (mms->mms_timeout > 0) {
+ gtimer_arm(&mms->mms_timer, mpegts_mux_sched_timer, mms,
+ mms->mms_timeout);
+ }
+ }
+
+ /* Cancel sub */
+ } else {
+ if (mms->mms_sub) {
+ subscription_unsubscribe(mms->mms_sub);
+ mms->mms_sub = NULL;
+ }
+ mms->mms_active = 0;
+
+ /* Find next */
+ if (cron_next(&mms->mms_cronjob, now, &nxt)) {
+ tvherror("muxsched", "failed to find next event");
+ return;
+ }
+
+ /* Timer */
+ gtimer_arm_abs(&mms->mms_timer, mpegts_mux_sched_timer, mms, nxt);
+ }
+}
+
+/******************************************************************************
+ * Init / Create
+ *****************************************************************************/
+
+mpegts_mux_sched_t *
+mpegts_mux_sched_create ( const char *uuid, htsmsg_t *conf )
+{
+ mpegts_mux_sched_t *mms;
+
+ if (!(mms = calloc(1, sizeof(mpegts_mux_sched_t)))) {
+ tvherror("muxsched", "calloc() failed");
+ assert(0);
+ return NULL;
+ }
+
+ /* Insert node */
+ idnode_insert(&mms->mms_id, uuid, &mpegts_mux_sched_class);
+
+ /* Add to list */
+ LIST_INSERT_HEAD(&mpegts_mux_sched_all, mms, mms_link);
+
+ /* Initialise */
+ streaming_target_init(&mms->mms_input, mpegts_mux_sched_input, mms, 0);
+
+ /* Load conf */
+ if (conf)
+ idnode_load(&mms->mms_id, conf);
+
+ /* Validate */
+ if (!mpegts_mux_find(mms->mms_mux ?: "") ||
+ !mms->mms_cronstr) {
+ mpegts_mux_sched_delete(mms, 1);
+ return NULL;
+ }
+
+ /* Set timer */
+ mpegts_mux_sched_set_timer(mms);
+
+ return mms;
+}
+
+void
+mpegts_mux_sched_save ( mpegts_mux_sched_t *mms )
+{
+ htsmsg_t *c = htsmsg_create_map();
+ idnode_save(&mms->mms_id, c);
+ hts_settings_save(c, "muxsched/%s", idnode_uuid_as_str(&mms->mms_id));
+ htsmsg_destroy(c);
+}
+
+void
+mpegts_mux_sched_delete ( mpegts_mux_sched_t *mms, int delconf )
+{
+ LIST_REMOVE(mms, mms_link);
+ if (delconf)
+ hts_settings_remove("muxsched/%s", idnode_uuid_as_str(&mms->mms_id));
+ if (mms->mms_sub)
+ subscription_unsubscribe(mms->mms_sub);
+ gtimer_disarm(&mms->mms_timer);
+ idnode_unlink(&mms->mms_id);
+ free(mms->mms_cronstr);
+ free(mms->mms_mux);
+ free(mms->mms_creator);
+ free(mms);
+}
+
+void
+mpegts_mux_sched_init ( void )
+{
+ htsmsg_t *c, *e;
+ htsmsg_field_t *f;
+
+ /* Load settings */
+ if ((c = hts_settings_load_r(1, "muxsched"))) {
+ HTSMSG_FOREACH(f, c) {
+ if (!(e = htsmsg_field_get_map(f))) continue;
+ mpegts_mux_sched_create(f->hmf_name, e);
+ }
+ htsmsg_destroy(c);
+ }
+}
+
+void
+mpegts_mux_sched_done ( void )
+{
+ mpegts_mux_sched_t *mms;
+ pthread_mutex_lock(&global_lock);
+ while ((mms = LIST_FIRST(&mpegts_mux_sched_all)))
+ mpegts_mux_sched_delete(mms, 0);
+ pthread_mutex_unlock(&global_lock);
+}
+
+/******************************************************************************
+ * Editor Configuration
+ *
+ * vim:sts=2:ts=2:sw=2:et
+ *****************************************************************************/
diff --git a/src/input/mpegts/mpegts_mux_sched.h b/src/input/mpegts/mpegts_mux_sched.h
new file mode 100644
index 00000000..f5dfa123
--- /dev/null
+++ b/src/input/mpegts/mpegts_mux_sched.h
@@ -0,0 +1,79 @@
+/*
+ * Tvheadend - TS file input system
+ *
+ * Copyright (C) 2014 Adam Sutton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __TVH_MPEGTS_MUX_SCHED_H__
+#define __TVH_MPEGTS_MUX_SCHED_H__
+
+#include "tvheadend.h"
+#include "cron.h"
+#include "idnode.h"
+#include "subscriptions.h"
+
+typedef LIST_HEAD(,mpegts_mux_sched) mpegts_mux_sched_list_t;
+
+extern mpegts_mux_sched_list_t mpegts_mux_sched_all;
+
+extern const idclass_t mpegts_mux_sched_class;
+
+typedef struct mpegts_mux_sched
+{
+ idnode_t mms_id;
+
+ LIST_ENTRY(mpegts_mux_sched) mms_link;
+
+ /*
+ * Configuration
+ */
+ int mms_enabled; ///< Enabled
+ char *mms_cronstr; ///< Cron configuration string
+ char *mms_mux; ///< Mux UUID
+ char *mms_creator; ///< Creator of entry
+ int mms_timeout; ///< Timeout (in seconds)
+ int mms_weight; ///< Weighting
+
+ /*
+ * Cron handling
+ */
+ int mms_active; ///< Subscription is active
+ gtimer_t mms_timer; ///< Timer for start/end
+ cron_t mms_cronjob; ///< Cron spec
+
+ /*
+ * Subscription
+ */
+ th_subscription_t *mms_sub; ///< Subscription handler
+ streaming_target_t mms_input; ///< Streaming input
+
+} mpegts_mux_sched_t;
+
+mpegts_mux_sched_t *mpegts_mux_sched_create ( const char *uuid, htsmsg_t *c );
+void mpegts_mux_sched_delete ( mpegts_mux_sched_t *mms, int delconf );
+void mpegts_mux_sched_save ( mpegts_mux_sched_t *mms );
+
+void mpegts_mux_sched_init ( void );
+void mpegts_mux_sched_done ( void );
+
+
+#endif /* __TVH_MPEGTS_H__ */
+
+/******************************************************************************
+ * Editor Configuration
+ *
+ * vim:sts=2:ts=2:sw=2:et
+ *****************************************************************************/
diff --git a/src/main.c b/src/main.c
index 93b0beb5..f23bec0e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -771,6 +771,9 @@ main(int argc, char **argv)
#if ENABLE_LINUXDVB
linuxdvb_init(adapter_mask);
#endif
+#if ENABLE_MPEGTS
+ mpegts_mux_sched_init();
+#endif
channel_init();
@@ -837,6 +840,9 @@ main(int argc, char **argv)
tvhftrace("main", webui_done);
tvhftrace("main", http_client_done);
tvhftrace("main", fsmonitor_done);
+#if ENABLE_MPEGTS
+ tvhftrace("main", mpegts_mux_sched_done);
+#endif
#if ENABLE_MPEGTS_DVB
tvhftrace("main", dvb_network_done);
#endif
diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js
index 2113db06..1e5f007c 100644
--- a/src/webui/static/app/mpegts.js
+++ b/src/webui/static/app/mpegts.js
@@ -137,3 +137,23 @@ tvheadend.services = function(panel)
}
});
}
+
+tvheadend.mux_sched = function(panel)
+{
+ tvheadend.idnode_grid(panel, {
+ url : 'api/mpegts/mux_sched',
+ comet : 'mpegts_mux_sched',
+ titleS : 'Mux Scheduler',
+ titleP : 'Mux Schedulers',
+ tabIndex : 4,
+ hidemode : true,
+ add : {
+ url : 'api/mpegts/mux_sched',
+ titleS : 'Mux Scheduler',
+ create : {
+ url : 'api/mpegts/mux_sched/create'
+ }
+ },
+ del : true
+ });
+}
diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js
index 4649c173..36b188bf 100644
--- a/src/webui/static/app/tvheadend.js
+++ b/src/webui/static/app/tvheadend.js
@@ -249,6 +249,7 @@ function accessUpdate(o) {
tvheadend.networks(tvheadend.conf_dvbin);
tvheadend.muxes(tvheadend.conf_dvbin);
tvheadend.services(tvheadend.conf_dvbin);
+ tvheadend.mux_sched(tvheadend.conf_dvbin);
tabs1.push(tvheadend.conf_dvbin);
/* Channel / EPG */