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 */