/* * tvheadend, WEBUI / HTML user interface * Copyright (C) 2008 Andreas Öman * * 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 . */ #define _GNU_SOURCE /* for splice() */ #include #include #include #include #include #include #include #include #include #include #include #include "tvheadend.h" #include "access.h" #include "http.h" #include "webui.h" #include "dvr/dvr.h" #include "filebundle.h" #include "psi.h" #include "plumbing/tsfix.h" #include "plumbing/globalheaders.h" #include "epg.h" #include "muxer.h" #include "dvb/dvb.h" #include "dvb/dvb_support.h" #include "imagecache.h" /** * */ static int is_client_simple(http_connection_t *hc) { char *c; if((c = http_arg_get(&hc->hc_args, "UA-OS")) != NULL) { if(strstr(c, "Windows CE") || strstr(c, "Pocket PC")) return 1; } if((c = http_arg_get(&hc->hc_args, "x-wap-profile")) != NULL) { return 1; } return 0; } /** * Root page, we direct the client to different pages depending * on if it is a full blown browser or just some mobile app */ static int page_root(http_connection_t *hc, const char *remain, void *opaque) { if(is_client_simple(hc)) { http_redirect(hc, "simple.html"); } else { http_redirect(hc, "extjs.html"); } return 0; } static int page_root2(http_connection_t *hc, const char *remain, void *opaque) { if (!tvheadend_webroot) return 1; char *tmp = malloc(strlen(tvheadend_webroot) + 2); sprintf(tmp, "%s/", tvheadend_webroot); http_redirect(hc, tmp); free(tmp); return 0; } /** * Static download of a file from the filesystem */ int page_static_file(http_connection_t *hc, const char *remain, void *opaque) { int ret = 0; const char *base = opaque; char path[500]; ssize_t size; const char *content = NULL, *postfix; char buf[4096]; const char *gzip; if(remain == NULL) return 404; if(strstr(remain, "..")) return HTTP_STATUS_BAD_REQUEST; snprintf(path, sizeof(path), "%s/%s", base, remain); postfix = strrchr(remain, '.'); if(postfix != NULL) { postfix++; if(!strcmp(postfix, "js")) content = "text/javascript; charset=UTF-8"; else if(!strcmp(postfix, "css")) content = "text/css; charset=UTF-8"; } // TODO: handle compression fb_file *fp = fb_open(path, 0, 1); if (!fp) { tvhlog(LOG_ERR, "webui", "failed to open %s", path); return 500; } size = fb_size(fp); gzip = fb_gzipped(fp) ? "gzip" : NULL; http_send_header(hc, 200, content, size, gzip, NULL, 10, 0, NULL); while (!fb_eof(fp)) { ssize_t c = fb_read(fp, buf, sizeof(buf)); if (c < 0) { ret = 500; break; } if (tvh_write(hc->hc_fd, buf, c)) { ret = 500; break; } } fb_close(fp); return ret; } /** * HTTP stream loop */ static void http_stream_run(http_connection_t *hc, streaming_queue_t *sq, const char *name, muxer_container_type_t mc) { streaming_message_t *sm; int run = 1; int started = 0; muxer_t *mux = NULL; int timeouts = 0; struct timespec ts; struct timeval tp; int err = 0; socklen_t errlen = sizeof(err); mux = muxer_create(mc); if(muxer_open_stream(mux, hc->hc_fd)) run = 0; /* reduce timeout on write() for streaming */ tp.tv_sec = 5; tp.tv_usec = 0; setsockopt(hc->hc_fd, SOL_SOCKET, SO_SNDTIMEO, &tp, sizeof(tp)); while(run) { pthread_mutex_lock(&sq->sq_mutex); sm = TAILQ_FIRST(&sq->sq_queue); if(sm == NULL) { gettimeofday(&tp, NULL); ts.tv_sec = tp.tv_sec + 1; ts.tv_nsec = tp.tv_usec * 1000; if(pthread_cond_timedwait(&sq->sq_cond, &sq->sq_mutex, &ts) == ETIMEDOUT) { timeouts++; //Check socket status getsockopt(hc->hc_fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); if(err) { tvhlog(LOG_DEBUG, "webui", "Stop streaming %s, client hung up", hc->hc_url_orig); run = 0; }else if(timeouts >= 20) { tvhlog(LOG_WARNING, "webui", "Stop streaming %s, timeout waiting for packets", hc->hc_url_orig); run = 0; } } pthread_mutex_unlock(&sq->sq_mutex); continue; } timeouts = 0; //Reset timeout counter TAILQ_REMOVE(&sq->sq_queue, sm, sm_link); pthread_mutex_unlock(&sq->sq_mutex); switch(sm->sm_type) { case SMT_MPEGTS: case SMT_PACKET: if(started) { muxer_write_pkt(mux, sm->sm_type, sm->sm_data); sm->sm_data = NULL; } break; case SMT_START: if(!started) { tvhlog(LOG_DEBUG, "webui", "Start streaming %s", hc->hc_url_orig); http_output_content(hc, muxer_mime(mux, sm->sm_data)); if(muxer_init(mux, sm->sm_data, name) < 0) run = 0; started = 1; } else if(muxer_reconfigure(mux, sm->sm_data) < 0) { tvhlog(LOG_WARNING, "webui", "Unable to reconfigure stream %s", hc->hc_url_orig); } break; case SMT_STOP: if(sm->sm_code != SM_CODE_SOURCE_RECONFIGURED) { tvhlog(LOG_WARNING, "webui", "Stop streaming %s, %s", hc->hc_url_orig, streaming_code2txt(sm->sm_code)); run = 0; } break; case SMT_SERVICE_STATUS: if(getsockopt(hc->hc_fd, SOL_SOCKET, SO_ERROR, &err, &errlen)) { tvhlog(LOG_DEBUG, "webui", "Stop streaming %s, client hung up", hc->hc_url_orig); run = 0; } break; case SMT_SKIP: case SMT_SPEED: case SMT_SIGNAL_STATUS: case SMT_TIMESHIFT_STATUS: break; case SMT_NOSTART: tvhlog(LOG_WARNING, "webui", "Couldn't start streaming %s, %s", hc->hc_url_orig, streaming_code2txt(sm->sm_code)); run = 0; break; case SMT_EXIT: tvhlog(LOG_WARNING, "webui", "Stop streaming %s, %s", hc->hc_url_orig, streaming_code2txt(sm->sm_code)); run = 0; break; } streaming_msg_free(sm); if(mux->m_errors) { tvhlog(LOG_WARNING, "webui", "Stop streaming %s, muxer reported errors", hc->hc_url_orig); run = 0; } } if(started) muxer_close(mux); muxer_destroy(mux); } /** * Output a playlist containing a single channel */ static int http_channel_playlist(http_connection_t *hc, channel_t *channel) { htsbuf_queue_t *hq; char buf[255]; const char *host; hq = &hc->hc_reply; host = http_arg_get(&hc->hc_args, "Host"); snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel->ch_id); htsbuf_qprintf(hq, "#EXTM3U\n"); htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel->ch_name); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, access_ticket_create(buf)); http_output_content(hc, "audio/x-mpegurl"); return 0; } /** * Output a playlist containing all channels with a specific tag */ static int http_tag_playlist(http_connection_t *hc, channel_tag_t *tag) { htsbuf_queue_t *hq; char buf[255]; channel_tag_mapping_t *ctm; const char *host; hq = &hc->hc_reply; host = http_arg_get(&hc->hc_args, "Host"); htsbuf_qprintf(hq, "#EXTM3U\n"); LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) { snprintf(buf, sizeof(buf), "/stream/channelid/%d", ctm->ctm_channel->ch_id); htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ctm->ctm_channel->ch_name); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, access_ticket_create(buf)); } http_output_content(hc, "audio/x-mpegurl"); return 0; } /** * Output a playlist pointing to tag-specific playlists */ static int http_tag_list_playlist(http_connection_t *hc) { htsbuf_queue_t *hq; char buf[255]; channel_tag_t *ct; const char *host; hq = &hc->hc_reply; host = http_arg_get(&hc->hc_args, "Host"); htsbuf_qprintf(hq, "#EXTM3U\n"); TAILQ_FOREACH(ct, &channel_tags, ct_link) { if(!ct->ct_enabled || ct->ct_internal) continue; snprintf(buf, sizeof(buf), "/playlist/tagid/%d", ct->ct_identifier); htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ct->ct_name); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, access_ticket_create(buf)); } http_output_content(hc, "audio/x-mpegurl"); return 0; } /** * Output a flat playlist with all channels */ static int http_channel_list_playlist(http_connection_t *hc) { htsbuf_queue_t *hq; char buf[255]; channel_t *ch; const char *host; hq = &hc->hc_reply; host = http_arg_get(&hc->hc_args, "Host"); htsbuf_qprintf(hq, "#EXTM3U\n"); RB_FOREACH(ch, &channel_name_tree, ch_name_link) { snprintf(buf, sizeof(buf), "/stream/channelid/%d", ch->ch_id); htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ch->ch_name); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, access_ticket_create(buf)); } http_output_content(hc, "audio/x-mpegurl"); return 0; } /** * Output a playlist of all recordings. */ static int http_dvr_list_playlist(http_connection_t *hc) { htsbuf_queue_t *hq; char buf[255]; dvr_entry_t *de; const char *host; off_t fsize; time_t durration; struct tm tm; int bandwidth; hq = &hc->hc_reply; host = http_arg_get(&hc->hc_args, "Host"); htsbuf_qprintf(hq, "#EXTM3U\n"); LIST_FOREACH(de, &dvrentries, de_global_link) { fsize = dvr_get_filesize(de); if(!fsize) continue; durration = de->de_stop - de->de_start; durration += (de->de_stop_extra + de->de_start_extra)*60; bandwidth = ((8*fsize) / (durration*1024.0)); strftime(buf, sizeof(buf), "%FT%T%z", localtime_r(&(de->de_start), &tm)); htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL)); htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration); htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%d,BANDWIDTH=%d\n", de->de_id, bandwidth); htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf); snprintf(buf, sizeof(buf), "/dvrfile/%d", de->de_id); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, access_ticket_create(buf)); } http_output_content(hc, "audio/x-mpegurl"); return 0; } /** * Output a playlist with a http stream for a dvr entry (.m3u format) */ static int http_dvr_playlist(http_connection_t *hc, dvr_entry_t *de) { htsbuf_queue_t *hq = &hc->hc_reply; char buf[255]; const char *ticket_id = NULL; time_t durration = 0; off_t fsize = 0; int bandwidth = 0; struct tm tm; const char *host = http_arg_get(&hc->hc_args, "Host"); durration = de->de_stop - de->de_start; durration += (de->de_stop_extra + de->de_start_extra)*60; fsize = dvr_get_filesize(de); if(fsize) { bandwidth = ((8*fsize) / (durration*1024.0)); strftime(buf, sizeof(buf), "%FT%T%z", localtime_r(&(de->de_start), &tm)); htsbuf_qprintf(hq, "#EXTM3U\n"); htsbuf_qprintf(hq, "#EXTINF:%"PRItime_t",%s\n", durration, lang_str_get(de->de_title, NULL)); htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%"PRItime_t"\n", durration); htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=%d,BANDWIDTH=%d\n", de->de_id, bandwidth); htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf); snprintf(buf, sizeof(buf), "/dvrfile/%d", de->de_id); ticket_id = access_ticket_create(buf); htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id); http_output_content(hc, "application/x-mpegURL"); } else { http_error(hc, HTTP_STATUS_NOT_FOUND); return HTTP_STATUS_NOT_FOUND; } return 0; } /** * Handle requests for playlists. */ static int page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) { char *components[2]; int nc, r; channel_t *ch = NULL; dvr_entry_t *de = NULL; channel_tag_t *tag = NULL; if(!remain) { http_redirect(hc, "/playlist/channels"); return HTTP_STATUS_FOUND; } nc = http_tokenize((char *)remain, components, 2, '/'); if(!nc) { http_error(hc, HTTP_STATUS_BAD_REQUEST); return HTTP_STATUS_BAD_REQUEST; } if(nc == 2) http_deescape(components[1]); pthread_mutex_lock(&global_lock); if(nc == 2 && !strcmp(components[0], "channelid")) ch = channel_find_by_identifier(atoi(components[1])); else if(nc == 2 && !strcmp(components[0], "channel")) ch = channel_find_by_name(components[1], 0, 0); else if(nc == 2 && !strcmp(components[0], "dvrid")) de = dvr_entry_find_by_id(atoi(components[1])); else if(nc == 2 && !strcmp(components[0], "tagid")) tag = channel_tag_find_by_identifier(atoi(components[1])); else if(nc == 2 && !strcmp(components[0], "tag")) tag = channel_tag_find_by_name(components[1], 0); if(ch) r = http_channel_playlist(hc, ch); else if(tag) r = http_tag_playlist(hc, tag); else if(de) r = http_dvr_playlist(hc, de); else if(!strcmp(components[0], "tags")) r = http_tag_list_playlist(hc); else if(!strcmp(components[0], "channels")) r = http_channel_list_playlist(hc); else if(!strcmp(components[0], "channels.m3u")) r = http_channel_list_playlist(hc); else if(!strcmp(components[0], "recordings")) r = http_dvr_list_playlist(hc); else { http_error(hc, HTTP_STATUS_BAD_REQUEST); r = HTTP_STATUS_BAD_REQUEST; } pthread_mutex_unlock(&global_lock); return r; } /** * Subscribes to a service and starts the streaming loop */ static int http_stream_service(http_connection_t *hc, service_t *service) { streaming_queue_t sq; th_subscription_t *s; streaming_target_t *gh; streaming_target_t *tsfix; streaming_target_t *st; dvr_config_t *cfg; muxer_container_type_t mc; int flags; const char *str; size_t qsize; const char *name; mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux")); if(mc == MC_UNKNOWN) { cfg = dvr_config_find_by_name_default(""); mc = cfg->dvr_mc; } if ((str = http_arg_get(&hc->hc_req_args, "qsize"))) qsize = atoll(str); else qsize = 1500000; if(mc == MC_PASS || mc == MC_RAW) { streaming_queue_init2(&sq, SMT_PACKET, qsize); gh = NULL; tsfix = NULL; st = &sq.sq_st; flags = SUBSCRIPTION_RAW_MPEGTS; } else { streaming_queue_init2(&sq, 0, qsize); gh = globalheaders_create(&sq.sq_st); tsfix = tsfix_create(gh); st = tsfix; flags = 0; } s = subscription_create_from_service(service, "HTTP", st, flags, inet_ntoa(hc->hc_peer->sin_addr), hc->hc_username, http_arg_get(&hc->hc_args, "User-Agent")); if(s) { name = strdupa(service->s_ch ? service->s_ch->ch_name : service->s_nicename); pthread_mutex_unlock(&global_lock); http_stream_run(hc, &sq, name, mc); pthread_mutex_lock(&global_lock); subscription_unsubscribe(s); } if(gh) globalheaders_destroy(gh); if(tsfix) tsfix_destroy(tsfix); streaming_queue_deinit(&sq); return 0; } /** * Subscribes to a service and starts the streaming loop */ #if ENABLE_LINUXDVB static int http_stream_tdmi(http_connection_t *hc, th_dvb_mux_instance_t *tdmi) { th_subscription_t *s; streaming_queue_t sq; const char *name; streaming_queue_init(&sq, SMT_PACKET); s = dvb_subscription_create_from_tdmi(tdmi, "HTTP", &sq.sq_st, inet_ntoa(hc->hc_peer->sin_addr), hc->hc_username, http_arg_get(&hc->hc_args, "User-Agent")); name = strdupa(tdmi->tdmi_identifier); pthread_mutex_unlock(&global_lock); http_stream_run(hc, &sq, name, MC_RAW); pthread_mutex_lock(&global_lock); subscription_unsubscribe(s); streaming_queue_deinit(&sq); return 0; } #endif /** * Subscribes to a channel and starts the streaming loop */ static int http_stream_channel(http_connection_t *hc, channel_t *ch) { streaming_queue_t sq; th_subscription_t *s; streaming_target_t *gh; streaming_target_t *tsfix; streaming_target_t *st; dvr_config_t *cfg; int priority = 100; int flags; muxer_container_type_t mc; char *str; size_t qsize; const char *name; mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux")); if(mc == MC_UNKNOWN) { cfg = dvr_config_find_by_name_default(""); mc = cfg->dvr_mc; } if ((str = http_arg_get(&hc->hc_req_args, "qsize"))) qsize = atoll(str); else qsize = 1500000; if(mc == MC_PASS || mc == MC_RAW) { streaming_queue_init2(&sq, SMT_PACKET, qsize); gh = NULL; tsfix = NULL; st = &sq.sq_st; flags = SUBSCRIPTION_RAW_MPEGTS; } else { streaming_queue_init2(&sq, 0, qsize); gh = globalheaders_create(&sq.sq_st); tsfix = tsfix_create(gh); st = tsfix; flags = 0; } s = subscription_create_from_channel(ch, priority, "HTTP", st, flags, inet_ntoa(hc->hc_peer->sin_addr), hc->hc_username, http_arg_get(&hc->hc_args, "User-Agent")); if(s) { name = strdupa(ch->ch_name); pthread_mutex_unlock(&global_lock); http_stream_run(hc, &sq, name, mc); pthread_mutex_lock(&global_lock); subscription_unsubscribe(s); } if(gh) globalheaders_destroy(gh); if(tsfix) tsfix_destroy(tsfix); streaming_queue_deinit(&sq); return 0; } /** * Handle the http request. http://tvheadend/stream/channelid/ * http://tvheadend/stream/channel/ * http://tvheadend/stream/service/ * http://tvheadend/stream/mux/ */ static int http_stream(http_connection_t *hc, const char *remain, void *opaque) { char *components[2]; channel_t *ch = NULL; service_t *service = NULL; #if ENABLE_LINUXDVB th_dvb_mux_instance_t *tdmi = NULL; #endif hc->hc_keep_alive = 0; if(remain == NULL) { http_error(hc, HTTP_STATUS_BAD_REQUEST); return HTTP_STATUS_BAD_REQUEST; } if(http_tokenize((char *)remain, components, 2, '/') != 2) { http_error(hc, HTTP_STATUS_BAD_REQUEST); return HTTP_STATUS_BAD_REQUEST; } http_deescape(components[1]); scopedgloballock(); if(!strcmp(components[0], "channelid")) { ch = channel_find_by_identifier(atoi(components[1])); } else if(!strcmp(components[0], "channel")) { ch = channel_find_by_name(components[1], 0, 0); } else if(!strcmp(components[0], "service")) { service = service_find_by_identifier(components[1]); #if ENABLE_LINUXDVB } else if(!strcmp(components[0], "mux")) { tdmi = dvb_mux_find_by_identifier(components[1]); #endif } if(ch != NULL) { return http_stream_channel(hc, ch); } else if(service != NULL) { return http_stream_service(hc, service); #if ENABLE_LINUXDVB } else if(tdmi != NULL) { return http_stream_tdmi(hc, tdmi); #endif } else { http_error(hc, HTTP_STATUS_BAD_REQUEST); return HTTP_STATUS_BAD_REQUEST; } } /** * Download a recorded file */ static int page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) { int fd, i; struct stat st; const char *content = NULL, *postfix, *range; dvr_entry_t *de; char *fname; char range_buf[255]; char disposition[256]; off_t content_len, file_start, file_end, chunk; ssize_t r; if(remain == NULL) return 404; pthread_mutex_lock(&global_lock); de = dvr_entry_find_by_id(atoi(remain)); if(de == NULL || de->de_filename == NULL) { pthread_mutex_unlock(&global_lock); return 404; } fname = strdup(de->de_filename); content = muxer_container_type2mime(de->de_mc, 1); postfix = muxer_container_suffix(de->de_mc, 1); pthread_mutex_unlock(&global_lock); fd = tvh_open(fname, O_RDONLY, 0); free(fname); if(fd < 0) return 404; if(fstat(fd, &st) < 0) { close(fd); return 404; } file_start = 0; file_end = st.st_size-1; range = http_arg_get(&hc->hc_args, "Range"); if(range != NULL) sscanf(range, "bytes=%"PRId64"-%"PRId64"", &file_start, &file_end); //Sanity checks if(file_start < 0 || file_start >= st.st_size) { close(fd); return 200; } if(file_end < 0 || file_end >= st.st_size) { close(fd); return 200; } if(file_start > file_end) { close(fd); return 200; } content_len = file_end - file_start+1; sprintf(range_buf, "bytes %"PRId64"-%"PRId64"/%"PRId64"", file_start, file_end, st.st_size); if(file_start > 0) lseek(fd, file_start, SEEK_SET); if(de->de_title != NULL) { snprintf(disposition, sizeof(disposition), "attachment; filename=%s.%s", lang_str_get(de->de_title, NULL), postfix); i = 20; while(disposition[i]) { if(disposition[i] == ' ') disposition[i] = '_'; i++; } } else { disposition[0] = 0; } http_send_header(hc, range ? HTTP_STATUS_PARTIAL_CONTENT : HTTP_STATUS_OK, content, content_len, NULL, NULL, 10, range ? range_buf : NULL, disposition[0] ? disposition : NULL); if(!hc->hc_no_output) { while(content_len > 0) { chunk = MIN(1024 * 1024 * 1024, content_len); r = sendfile(hc->hc_fd, fd, NULL, chunk); if(r == -1) { close(fd); return -1; } content_len -= r; } } close(fd); return 0; } /** * Fetch image cache image */ /** * Static download of a file from the filesystem */ static int page_imagecache(http_connection_t *hc, const char *remain, void *opaque) { uint32_t id; int fd; char buf[8192]; struct stat st; ssize_t c; if(remain == NULL) return 404; if(sscanf(remain, "%d", &id) != 1) return HTTP_STATUS_BAD_REQUEST; if ((fd = imagecache_open(id)) < 0) return 404; if (fstat(fd, &st)) { close(fd); return 404; } http_send_header(hc, 200, NULL, st.st_size, 0, NULL, 10, 0, NULL); while (1) { c = read(fd, buf, sizeof(buf)); if (c <= 0) break; if (tvh_write(hc->hc_fd, buf, c)) break; } close(fd); return 0; } /** * */ static void webui_static_content(const char *http_path, const char *source) { http_path_add(http_path, strdup(source), page_static_file, ACCESS_WEB_INTERFACE); } /** * */ static int favicon(http_connection_t *hc, const char *remain, void *opaque) { http_redirect(hc, "static/htslogo.png"); return 0; } int page_statedump(http_connection_t *hc, const char *remain, void *opaque); /** * WEB user interface */ void webui_init(void) { if (tvheadend_webui_debug) tvhlog(LOG_INFO, "webui", "Running web interface in debug mode"); http_path_add("", NULL, page_root2, ACCESS_WEB_INTERFACE); http_path_add("/", NULL, page_root, ACCESS_WEB_INTERFACE); http_path_add("/dvrfile", NULL, page_dvrfile, ACCESS_WEB_INTERFACE); http_path_add("/favicon.ico", NULL, favicon, ACCESS_WEB_INTERFACE); http_path_add("/playlist", NULL, page_http_playlist, ACCESS_WEB_INTERFACE); http_path_add("/state", NULL, page_statedump, ACCESS_ADMIN); http_path_add("/stream", NULL, http_stream, ACCESS_STREAMING); http_path_add("/imagecache", NULL, page_imagecache, ACCESS_WEB_INTERFACE); webui_static_content("/static", "src/webui/static"); webui_static_content("/docs", "docs/html"); webui_static_content("/docresources", "docs/docresources"); simpleui_start(); extjs_start(); comet_init(); }