summaryrefslogtreecommitdiff
path: root/src/vdr-plugin/webvideo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vdr-plugin/webvideo.c')
-rw-r--r--src/vdr-plugin/webvideo.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/vdr-plugin/webvideo.c b/src/vdr-plugin/webvideo.c
new file mode 100644
index 0000000..554ef28
--- /dev/null
+++ b/src/vdr-plugin/webvideo.c
@@ -0,0 +1,444 @@
+/*
+ * webvideo.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <getopt.h>
+#include <time.h>
+#include <vdr/plugin.h>
+#include <vdr/tools.h>
+#include <vdr/videodir.h>
+#include <vdr/i18n.h>
+#include <vdr/skins.h>
+#include <libwebvi.h>
+#include "menu.h"
+#include "history.h"
+#include "download.h"
+#include "request.h"
+#include "mimetypes.h"
+#include "config.h"
+#include "player.h"
+#include "common.h"
+#include "timer.h"
+
+const char *VERSION = "0.3.0";
+static const char *DESCRIPTION = trNOOP("Download video files from the web");
+static const char *MAINMENUENTRY = "Webvideo";
+cMimeTypes *MimeTypes = NULL;
+
+class cPluginWebvideo : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+ cHistory history;
+ cProgressVector summaries;
+ cString templatedir;
+ cString destdir;
+ cString conffile;
+
+ static int nextMenuID;
+
+ void UpdateOSDFromHistory(const char *statusmsg=NULL);
+ void UpdateStatusMenu(bool force=false);
+ bool StartStreaming(const cString &streamurl);
+ void ExecuteTimers(void);
+ void HandleFinishedRequests(void);
+
+public:
+ cPluginWebvideo(void);
+ virtual ~cPluginWebvideo();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return tr(DESCRIPTION); }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+ };
+
+int cPluginWebvideo::nextMenuID = 1;
+
+cPluginWebvideo::cPluginWebvideo(void)
+{
+ // Initialize any member variables here.
+ // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+ // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+}
+
+cPluginWebvideo::~cPluginWebvideo()
+{
+ // Clean up after yourself!
+ webvi_cleanup(0);
+}
+
+const char *cPluginWebvideo::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return " -d DIR, --downloaddir=DIR Save downloaded files to DIR\n" \
+ " -t DIR, --templatedir=DIR Read video site templates from DIR\n" \
+ " -c FILE, --conf=FILE Load settings from FILE\n";
+}
+
+bool cPluginWebvideo::ProcessArgs(int argc, char *argv[])
+{
+ // Implement command line argument processing here if applicable.
+ static struct option long_options[] = {
+ { "downloaddir", required_argument, NULL, 'd' },
+ { "templatedir", required_argument, NULL, 't' },
+ { "conf", required_argument, NULL, 'c' },
+ { NULL }
+ };
+
+ int c;
+ while ((c = getopt_long(argc, argv, "d:t:c:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ destdir = cString(optarg);
+ break;
+ case 't':
+ templatedir = cString(optarg);
+ break;
+ case 'c':
+ conffile = cString(optarg);
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cPluginWebvideo::Initialize(void)
+{
+ // Initialize any background activities the plugin shall perform.
+
+ // Test that run-time and compile-time libxml versions are compatible
+ LIBXML_TEST_VERSION;
+
+ // default values if not given on the command line
+ if ((const char *)destdir == NULL)
+ destdir = cString(VideoDirectory);
+ if ((const char *)conffile == NULL)
+ conffile = AddDirectory(ConfigDirectory(Name()), "webvi.plugin.conf");
+
+ webvideoConfig->SetDownloadPath(destdir);
+ webvideoConfig->SetTemplatePath(templatedir);
+ webvideoConfig->ReadConfigFile(conffile);
+
+ cString mymimetypes = AddDirectory(ConfigDirectory(Name()), "mime.types");
+ const char *mimefiles [] = {"/etc/mime.types", (const char *)mymimetypes, NULL};
+ MimeTypes = new cMimeTypes(mimefiles);
+
+ if (webvi_global_init() != 0) {
+ error("Failed to initialize libwebvi");
+ return false;
+ }
+
+ cWebviTimerManager::Instance().Load(ConfigDirectory(Name()));
+
+ cWebviThread::Instance().SetTemplatePath(webvideoConfig->GetTemplatePath());
+
+ return true;
+}
+
+bool cPluginWebvideo::Start(void)
+{
+ // Start any background activities the plugin shall perform.
+ cWebviThread::Instance().Start();
+
+ return true;
+}
+
+void cPluginWebvideo::Stop(void)
+{
+ // Stop any background activities the plugin shall perform.
+ cWebviThread::Instance().Stop();
+ delete MimeTypes;
+
+ cWebviTimerManager::Instance().Save(ConfigDirectory(Name()));
+
+ xmlCleanupParser();
+}
+
+void cPluginWebvideo::Housekeeping(void)
+{
+ // Perform any cleanup or other regular tasks.
+
+ cWebviTimerManager::Instance().Save(ConfigDirectory(Name()));
+}
+
+void cPluginWebvideo::MainThreadHook(void)
+{
+ // Perform actions in the context of the main program thread.
+ // WARNING: Use with great care - see PLUGINS.html!
+ ExecuteTimers();
+
+ HandleFinishedRequests();
+}
+
+void cPluginWebvideo::ExecuteTimers(void)
+{
+ static int counter = 0;
+
+ // don't do this too often
+ if (counter++ > 1800) {
+ cWebviTimerManager::Instance().Update();
+ counter = 0;
+ }
+}
+
+void cPluginWebvideo::HandleFinishedRequests(void)
+{
+ bool forceStatusUpdate = false;
+ cMenuRequest *req;
+ cFileDownloadRequest *dlreq;
+ cString streamurl;
+ cWebviTimer *timer;
+ cString timermsg;
+
+ while ((req = cWebviThread::Instance().GetFinishedRequest())) {
+ int cid = -1;
+ int code = req->GetStatusCode();
+ if (history.Current()) {
+ cid = history.Current()->GetID();
+ }
+
+ debug("Finished request: %d (current: %d), type = %d, status = %d",
+ req->GetID(), cid, req->GetType(), code);
+
+ if (req->Success()) {
+ switch (req->GetType()) {
+ case REQT_MENU:
+ // Only change the menu if the request was launched from the
+ // current menu.
+ if (req->GetID() == cid) {
+ if (cid == 0) {
+ // Special case: replace the placeholder menu
+ history.Clear();
+ }
+
+ if (history.Current())
+ history.Current()->RememberSelected(menuPointers.navigationMenu->Current());
+ history.TruncateAndAdd(new cHistoryObject(req->GetResponse(),
+ req->GetReference(),
+ nextMenuID++));
+ UpdateOSDFromHistory();
+ }
+ break;
+
+ case REQT_STREAM:
+ streamurl = req->GetResponse();
+ if (streamurl[0] == '\0')
+ Skins.Message(mtError, tr("Streaming failed: no URL"));
+ else if (!StartStreaming(streamurl))
+ Skins.Message(mtError, tr("Failed to launch media player"));
+ break;
+
+ case REQT_FILE:
+ dlreq = dynamic_cast<cFileDownloadRequest *>(req);
+
+ if (dlreq) {
+ for (int i=0; i<summaries.Size(); i++) {
+ if (summaries[i]->GetRequest() == dlreq) {
+ delete summaries[i];
+ summaries.Remove(i);
+ break;
+ }
+ }
+ }
+
+ timermsg = cString("");
+ if (req->GetTimer()) {
+ req->GetTimer()->RequestFinished(req->GetReference(), NULL);
+
+ timermsg = cString::sprintf(" (%s)", tr("timer"));
+ }
+
+ Skins.Message(mtInfo, cString::sprintf(tr("One download completed, %d remains%s"),
+ cWebviThread::Instance().GetUnfinishedCount(),
+ (const char *)timermsg));
+ forceStatusUpdate = true;
+ break;
+
+ case REQT_TIMER:
+ timer = req->GetTimer();
+ if (timer)
+ timer->DownloadStreams(req->GetResponse(), summaries);
+ break;
+
+ default:
+ break;
+ }
+ } else { // failed request
+ if (req->GetType() == REQT_TIMER) {
+ warning("timer request failed (%d: %s)",
+ code, (const char*)req->GetStatusPharse());
+
+ timer = req->GetTimer();
+ if (timer)
+ timer->CheckFailed(req->GetStatusPharse());
+ } else {
+ warning("request failed (%d: %s)",
+ code, (const char*)req->GetStatusPharse());
+
+ if (code == -2 || code == 402)
+ Skins.Message(mtError, tr("Download aborted"));
+ else
+ Skins.Message(mtError, cString::sprintf(tr("Download failed (error = %d)"), code));
+
+ dlreq = dynamic_cast<cFileDownloadRequest *>(req);
+ if (dlreq) {
+ for (int i=0; i<summaries.Size(); i++) {
+ if (summaries[i]->GetRequest() == dlreq) {
+ summaries[i]->AssociateWith(NULL);
+ break;
+ }
+ }
+ }
+
+ if (req->GetTimer())
+ req->GetTimer()->RequestFinished(req->GetReference(),
+ (const char*)req->GetStatusPharse());
+
+ forceStatusUpdate = true;
+ }
+ }
+
+ delete req;
+ }
+
+ UpdateStatusMenu(forceStatusUpdate);
+}
+
+cString cPluginWebvideo::Active(void)
+{
+ // Return a message string if shutdown should be postponed
+ int c = cWebviThread::Instance().GetUnfinishedCount();
+ if (c > 0)
+ return cString::sprintf(tr("%d downloads not finished"), c);
+ else
+ return NULL;
+}
+
+cOsdObject *cPluginWebvideo::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ const char *mainMenuReference = "wvt:///?srcurl=mainmenu";
+ const char *placeholderMenu = "<wvmenu><title>Webvideo</title></wvmenu>";
+ const char *statusmsg = NULL;
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100*1000*1000; // 100 ms
+
+ menuPointers.navigationMenu = new cNavigationMenu(&history, summaries);
+
+ cHistoryObject *hist = history.Home();
+ if (!hist) {
+ cWebviThread::Instance().AddRequest(new cMenuRequest(0, mainMenuReference));
+ cHistoryObject *placeholder = new cHistoryObject(placeholderMenu, mainMenuReference, 0);
+ history.TruncateAndAdd(placeholder);
+
+ // The main menu response should come right away. Try to update
+ // the menu here without having to wait for the next
+ // MainThreadHook call by VDR main loop.
+ for (int i=0; i<4; i++) {
+ nanosleep(&ts, NULL);
+ HandleFinishedRequests();
+ if (history.Current() != placeholder) {
+ return menuPointers.navigationMenu;
+ }
+ };
+
+ statusmsg = tr("Retrieving...");
+ }
+
+ UpdateOSDFromHistory(statusmsg);
+ return menuPointers.navigationMenu;
+}
+
+cMenuSetupPage *cPluginWebvideo::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return NULL;
+}
+
+bool cPluginWebvideo::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+bool cPluginWebvideo::Service(const char *Id, void *Data)
+{
+ // Handle custom service requests from other plugins
+ return false;
+}
+
+const char **cPluginWebvideo::SVDRPHelpPages(void)
+{
+ // Return help text for SVDRP commands this plugin implements
+ return NULL;
+}
+
+cString cPluginWebvideo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+ // Process SVDRP commands this plugin implements
+ return NULL;
+}
+
+void cPluginWebvideo::UpdateOSDFromHistory(const char *statusmsg) {
+ if (menuPointers.navigationMenu) {
+ cHistoryObject *hist = history.Current();
+ menuPointers.navigationMenu->Populate(hist, statusmsg);
+ menuPointers.navigationMenu->Display();
+ } else {
+ debug("OSD is not ours.");
+ }
+}
+
+void cPluginWebvideo::UpdateStatusMenu(bool force) {
+ if (menuPointers.statusScreen &&
+ (force || menuPointers.statusScreen->NeedsUpdate())) {
+ menuPointers.statusScreen->Update();
+ }
+}
+
+bool cPluginWebvideo::StartStreaming(const cString &streamurl) {
+ cMediaPlayer *players[2];
+
+ if (webvideoConfig->GetPreferXineliboutput()) {
+ players[0] = new cXineliboutputPlayer();
+ players[1] = new cMPlayerPlayer();
+ } else {
+ players[0] = new cMPlayerPlayer();
+ players[1] = new cXineliboutputPlayer();
+ }
+
+ bool ret = false;
+ for (int i=0; i<2; i++) {
+ if (players[i]->Launch(streamurl)) {
+ ret = true;
+ break;
+ }
+ }
+
+ for (int i=0; i<2 ; i++) {
+ delete players[i];
+ }
+
+ return ret;
+}
+
+VDRPLUGINCREATOR(cPluginWebvideo); // Don't touch this!