diff -r cb885ee44618 -r c5a9d9583e64 maemo-ui-old/src/mmyth_tvplayer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maemo-ui-old/src/mmyth_tvplayer.c Mon Nov 19 17:57:40 2007 +0000 @@ -0,0 +1,712 @@ +/** + * GMyth Library + * + * @file gmyth/mmyth_tvplayer.c + * + * @brief

This component provides playback of the remote A/V using + * GStreamer. + * + * Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia. + * @author Hallyson Luiz de Morais Melo + * + *//* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "mmyth_tvplayer.h" + +#include + +#include + +#define MYTHTV_VERSION_DEFAULT 30 + +typedef struct _GstPlayerWindowStateChange { + GstElement *play; + GstState old_state, + new_state; + MMythTVPlayer *tvplayer; +} GstPlayerWindowStateChange; + +typedef struct _GstPlayerWindowTagFound { + GstElement *play; + GstTagList *taglist; + MMythTVPlayer *tvplayer; +} GstPlayerWindowTagFound; + +/* + * static gboolean idle_state (gpointer data); + */ +static gboolean bus_call(GstBus * bus, GstMessage * msg, gpointer data); + +static void mmyth_tvplayer_class_init(MMythTVPlayerClass * klass); +static void mmyth_tvplayer_init(MMythTVPlayer * object); + +static void mmyth_tvplayer_dispose(GObject * object); +static void mmyth_tvplayer_finalize(GObject * object); + +G_DEFINE_TYPE(MMythTVPlayer, mmyth_tvplayer, G_TYPE_OBJECT) + + static gboolean mmyth_tvplayer_create_pipeline(MMythTVPlayer * + tvplayer); + static void new_pad_cb(GstElement * element, GstPad * pad, + gpointer data); + + static gboolean expose_cb(GtkWidget * widget, + GdkEventExpose * event, gpointer user_data); + + static void + mmyth_tvplayer_class_init(MMythTVPlayerClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + gobject_class->dispose = mmyth_tvplayer_dispose; + gobject_class->finalize = mmyth_tvplayer_finalize; +} + +static void +new_pad_cb(GstElement * element, GstPad * pad, gpointer data) +{ + MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(data); + GstPadLinkReturn ret; + char *s; + + s = gst_caps_to_string(pad->caps); + + if (s[0] == 'a') { + ret = + gst_pad_link(pad, + gst_element_get_pad(tvplayer->audioqueue1, + "sink")); + } else { + ret = + gst_pad_link(pad, + gst_element_get_pad(tvplayer->videoqueue1, + "sink")); + } + + g_free(s); +} + +static gboolean +expose_cb(GtkWidget * widget, GdkEventExpose * event, gpointer user_data) +{ + MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(user_data); + + if (tvplayer && tvplayer->videow) { + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY + (tvplayer->gst_videosink), + GDK_WINDOW_XWINDOW(widget->window)); + return TRUE; + } + + g_warning("MMythTVPlayer expose called before setting video window\n"); + + return FALSE; +} + +static void +mmyth_tvplayer_init(MMythTVPlayer * tvplayer) +{ + tvplayer->gst_pipeline = NULL; + tvplayer->gst_source = NULL; + tvplayer->gst_videodec = NULL; + tvplayer->gst_videosink = NULL; + tvplayer->gst_videocolortrs = NULL; + tvplayer->videoqueue1 = NULL; + tvplayer->videoqueue2 = NULL; + tvplayer->audioqueue1 = NULL; + tvplayer->audioqueue2 = NULL; + + /* + * GTKWidget for rendering the video + */ + tvplayer->videow = NULL; + tvplayer->expose_handler = 0; + + tvplayer->backend_hostname = NULL; + tvplayer->backend_port = 0; + tvplayer->local_hostname = NULL; + + tvplayer->recorder = NULL; + tvplayer->tvchain = NULL; + tvplayer->proginfo = NULL; +} + +static void +mmyth_tvplayer_dispose(GObject * object) +{ + + G_OBJECT_CLASS(mmyth_tvplayer_parent_class)->dispose(object); +} + +static void +mmyth_tvplayer_finalize(GObject * object) +{ + g_signal_handlers_destroy(object); + + MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(object); + + g_debug("[%s] Finalizing tvplayer", __FUNCTION__); + + if (tvplayer->videow != NULL) { + if (g_signal_handler_is_connected(tvplayer->videow, + tvplayer->expose_handler)) { + g_signal_handler_disconnect(tvplayer->videow, + tvplayer->expose_handler); + } + g_object_unref(tvplayer->videow); + } + + if (tvplayer->recorder != NULL) + g_object_unref(tvplayer->recorder); + if (tvplayer->tvchain != NULL) + g_object_unref(tvplayer->tvchain); + if (tvplayer->proginfo != NULL) + g_object_unref(tvplayer->proginfo); + + // Release Gstreamer elements + if (tvplayer->gst_pipeline != NULL) + g_object_unref(tvplayer->gst_pipeline); + if (tvplayer->gst_source != NULL) + g_object_unref(tvplayer->gst_source); + if (tvplayer->gst_videodec != NULL) + g_object_unref(tvplayer->gst_videodec); + if (tvplayer->gst_videocolortrs != NULL) + g_object_unref(tvplayer->gst_videocolortrs); + if (tvplayer->gst_videosink != NULL) + g_object_unref(tvplayer->gst_videosink); + if (tvplayer->videoqueue1 != NULL) + g_object_unref(tvplayer->videoqueue1); + if (tvplayer->videoqueue2 != NULL) + g_object_unref(tvplayer->videoqueue2); + if (tvplayer->audioqueue1 != NULL) + g_object_unref(tvplayer->audioqueue1); + if (tvplayer->audioqueue2 != NULL) + g_object_unref(tvplayer->audioqueue2); + + G_OBJECT_CLASS(mmyth_tvplayer_parent_class)->finalize(object); +} + +/** Creates a new instance of MMythTVPlayer. + * + * @return a new instance of MMythTVPlayer. + */ +MMythTVPlayer * +mmyth_tvplayer_new() +{ + MMythTVPlayer *tvplayer = + MMYTH_TVPLAYER(g_object_new(MMYTH_TVPLAYER_TYPE, NULL)); + + return tvplayer; +} + +/** Initializes the tv player. + * + * @param tvplayer the object instance. + * @return gboolean TRUE if the pipeline was created + * successfully, FALSE otherwise. + */ +gboolean +mmyth_tvplayer_initialize(MMythTVPlayer * tvplayer, + GMythBackendInfo * backend_info) +{ + tvplayer->backend_info = backend_info; + + if (!mmyth_tvplayer_create_pipeline(tvplayer)) { + g_warning + ("[%s] Error while creating pipeline. TV Player not initialized", + __FUNCTION__); + return FALSE; + } else { + g_debug("[%s] GStreamer pipeline created", __FUNCTION__); + } + + return TRUE; +} + +/** Creates the GStreamer pipeline used by the player. + * + * @param tvplayer the object instance. + * @return gboolean TRUE if the pipeline was created + * successfully, FALSE otherwise. + */ +static gboolean +mmyth_tvplayer_create_pipeline(MMythTVPlayer * tvplayer) +{ + GstElement *pipeline; + GstElement *source, + *parser; + GstElement *videodec, + *videosink; + GstElement *videocolortrs; +#ifndef MAEMO_PLATFORM + GstElement *audiodec, + *audioconv, + *audioqueue2; +#endif + GstElement *audiosink; + GstElement *videoqueue1, + *videoqueue2, + *audioqueue1; + + g_debug("MMythTVPlayer: Setting the Gstreamer pipeline\n"); + + pipeline = gst_pipeline_new("video-player"); + source = gst_element_factory_make("mythtvsrc", "myth-source"); + parser = gst_element_factory_make("nuvdemux", "nuv-demux"); + + /* + * Gstreamer Video elements + */ + videoqueue1 = gst_element_factory_make("queue", "video-queue1"); + videodec = gst_element_factory_make("ffdec_mpeg4", "video-decoder"); + videoqueue2 = gst_element_factory_make("queue", "video-queue2"); + videocolortrs = + gst_element_factory_make("ffmpegcolorspace", + "image-color-transforms"); + +#ifdef MAEMO_PLATFORM + videosink = gst_element_factory_make("sdlvideosink", "image-output"); +#else + videosink = gst_element_factory_make("xvimagesink", "image-output"); +#endif + + /* + * Gstreamer Audio elements + */ + audioqueue1 = gst_element_factory_make("queue", "audio-queue1"); +#ifdef MAEMO_PLATFORM + audiosink = gst_element_factory_make("dspmp3sink", "audio-output"); +#else + audioqueue2 = gst_element_factory_make("queue", "audio-queue2"); + audiodec = gst_element_factory_make("mad", "audio-decoder"); + audioconv = + gst_element_factory_make("audioconvert", "audio-converter"); + audiosink = gst_element_factory_make("alsasink", "audio-output"); +#endif + + if (!(pipeline && source && parser && videodec && videosink) || + !(videoqueue1 && videoqueue2 && audioqueue1 && audiosink)) { + /* + * FIXME: hanlde the error correctly + */ + /* + * video_alignment is not being created (below) and is causing + * problems to the ui + */ + + tvplayer->gst_pipeline = NULL; + tvplayer->gst_videodec = NULL; + tvplayer->gst_videosink = NULL; + tvplayer->gst_videocolortrs = NULL; + + g_warning("GstElement creation error!\n"); + return FALSE; + } +#ifndef MAEMO_PLATFORM + if (!(audiodec && audioconv)) { + g_warning("GstElement for audio stream creation error!"); + return FALSE; + } +#endif + + tvplayer->gst_pipeline = pipeline; + tvplayer->gst_source = source; + tvplayer->gst_videodec = videodec; + tvplayer->gst_videosink = videosink; + tvplayer->gst_videocolortrs = videocolortrs; + g_object_ref(tvplayer->gst_pipeline); + g_object_ref(tvplayer->gst_source); + g_object_ref(tvplayer->gst_videodec); + g_object_ref(tvplayer->gst_videosink); + g_object_ref(tvplayer->gst_videocolortrs); + + tvplayer->videoqueue1 = videoqueue1; + tvplayer->videoqueue2 = videoqueue2; + tvplayer->audioqueue1 = audioqueue1; + g_object_ref(tvplayer->videoqueue1); + g_object_ref(tvplayer->videoqueue2); + g_object_ref(tvplayer->audioqueue1); + +#ifndef MAEMO_PLATFORM + tvplayer->audioqueue2 = audioqueue2; + g_object_ref(tvplayer->audioqueue2); +#endif + + // g_object_set (G_OBJECT (videosink), "sync", TRUE, NULL); + g_object_set(G_OBJECT(audiosink), "sync", FALSE, NULL); + + gst_bus_add_watch(gst_pipeline_get_bus + (GST_PIPELINE(tvplayer->gst_pipeline)), bus_call, + tvplayer); + + gst_bin_add_many(GST_BIN(pipeline), source, parser, videoqueue1, + videodec, videoqueue2, videocolortrs, videosink, + NULL); + +#ifndef MAEMO_PLATFORM + gst_bin_add_many(GST_BIN(pipeline), audioqueue1, audiodec, audioconv, + audioqueue2, audiosink, NULL); +#else + gst_bin_add_many(GST_BIN(pipeline), audioqueue1, audiosink, NULL); +#endif + + { + // GstCaps *rtpcaps = gst_caps_new_simple ("application/x-rtp", + // NULL); + // gst_element_link_filtered(source, parser, rtpcaps); + } + + gst_element_link(source, parser); + gst_element_link_many(videoqueue1, videodec, videoqueue2, + videocolortrs, videosink, NULL); + +#ifndef MAEMO_PLATFORM + gst_element_link_many(videosink, audioqueue1, audiodec, audioconv, + audioqueue2, audiosink, NULL); +#else + gst_element_link_many(videosink, audioqueue1, audiosink, NULL); +#endif + + g_signal_connect(parser, "pad-added", G_CALLBACK(new_pad_cb), + tvplayer); + + return TRUE; +} + +/** Configures the backend and the tv player + * for playing the recorded content A/V. + * + * FIXME: Change filename to program info or other structure about the recorded + * + * @param tvplayer the object instance. + * @param filename the file uri of the recorded content to be played. + * @return TRUE if successfull, FALSE if any error happens. + */ +gboolean +mmyth_tvplayer_record_setup(MMythTVPlayer * tvplayer, + const gchar * filename) +{ + // FIXME: we should receive the uri instead of filename + const gchar *hostname = + gmyth_backend_info_get_hostname(tvplayer->backend_info); + const gint port = + gmyth_backend_info_get_port(tvplayer->backend_info); + + GString *fullpath = g_string_new("myth://"); + g_string_append_printf(fullpath, "%s:%d/%s", hostname, port, filename); + + tvplayer->is_livetv = FALSE; + + g_debug("[%s] Setting record uri to gstreamer pipeline to %s", + __FUNCTION__, fullpath->str); + + g_object_set(G_OBJECT(tvplayer->gst_source), "location", + fullpath->str, NULL); + + return TRUE; +} + +/** Configures the backend and the tv player + * for playing the live tv. + * + * @param tvplayer the object instance. + * @return TRUE if successfull, FALSE if any error happens. + */ +gboolean +mmyth_tvplayer_livetv_setup(MMythTVPlayer * tvplayer) +{ + gboolean res = TRUE; + + tvplayer->livetv = gmyth_livetv_new(); + + if (!gmyth_livetv_setup(tvplayer->livetv, tvplayer->backend_info)) + goto error; + + return res; + + error: + res = FALSE; + if (tvplayer->livetv != NULL) { + g_object_unref(tvplayer->livetv); + } + + if (tvplayer->local_hostname != NULL) { + g_string_free(tvplayer->local_hostname, TRUE); + } + + if (tvplayer->recorder != NULL) { + g_object_unref(tvplayer->recorder); + tvplayer->recorder = NULL; + } + + if (tvplayer->tvchain != NULL) { + g_object_unref(tvplayer->tvchain); + tvplayer->tvchain = NULL; + } + + if (tvplayer->proginfo != NULL) { + g_object_unref(tvplayer->proginfo); + tvplayer->proginfo = NULL; + } + + return res; + +} + +/** Sets the GTK video widget for the tv player. + * + * @param tvplayer the object instance. + * @param videow the GTK video window. + * @return TRUE if successfull, FALSE if any error happens. + */ +gboolean +mmyth_tvplayer_set_widget(MMythTVPlayer * tvplayer, GtkWidget * videow) +{ + tvplayer->videow = videow; + g_object_ref(videow); + + g_debug("[%s] Setting widget for tv player render", __FUNCTION__); + + tvplayer->expose_handler = + g_signal_connect(tvplayer->videow, "expose-event", + G_CALLBACK(expose_cb), tvplayer); + + // g_signal_connect(miptv_ui->videow, "size_request", + // G_CALLBACK(cb_preferred_video_size), miptv_ui); + + return TRUE; +} + +static gboolean +bus_call(GstBus * bus, GstMessage * msg, gpointer data) +{ + // MMythTVPlayer *tvplayer = MMYTH_TVPLAYER ( data ); + // GMainLoop *loop = tvplayer->loop; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + printf("End of stream\n"); + // g_idle_add ((GSourceFunc) idle_eos, data); + gst_element_set_state(GST_ELEMENT(GST_MESSAGE_SRC(msg)), + GST_STATE_NULL); + gst_element_set_locked_state(GST_ELEMENT(GST_MESSAGE_SRC(msg)), + TRUE); + break; + case GST_MESSAGE_ERROR: + { + gchar *debug; + GError *err; + + gst_message_parse_error(msg, &err, &debug); + g_free(debug); + + printf("Error: %s\n", err->message); + g_error_free(err); + + // g_main_loop_quit (loop); + } + break; + default: + printf(gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); + printf("\n"); + break; + } + + return TRUE; +} + + +#if 0 +static gboolean +idle_state(gpointer data) +{ + GstPlayerWindowStateChange *st = data; + + if (st->old_state == GST_STATE_PLAYING) { + if (st->miptv_ui->idle_id != 0) { + g_source_remove(st->miptv_ui->idle_id); + st->miptv_ui->idle_id = 0; + } + } else if (st->new_state == GST_STATE_PLAYING) { + if (st->miptv_ui->idle_id != 0) + g_source_remove(st->miptv_ui->idle_id); + + st->miptv_ui->idle_id = g_idle_add(cb_iterate, st->miptv_ui); + } + + /* + * new movie loaded? + */ + if (st->old_state == GST_STATE_READY + && st->new_state > GST_STATE_READY) { + + /* + * gboolean have_video = FALSE; + */ + + gtk_widget_show(st->miptv_ui->videow); + + gtk_window_resize(GTK_WINDOW(st->miptv_ui->main_window), 1, 1); + + } + + /* + * discarded movie? + */ + if (st->old_state > GST_STATE_READY + && st->new_state == GST_STATE_READY) { + + if (st->miptv_ui->tagcache) { + gst_tag_list_free(st->miptv_ui->tagcache); + st->miptv_ui->tagcache = NULL; + } + } + + gst_object_unref(GST_OBJECT(st->play)); + // g_object_unref (G_OBJECT (st->win)); + g_free(st); + + /* + * once + */ + return FALSE; +} + +#endif + +/** Stops playing the current A/V. + * + * FIXME: How to proceed differently between livetv + * and recorded content? + * + * @param tvplayer the object instance. + * @return void + */ +void +mmyth_tvplayer_stop_playing(MMythTVPlayer * tvplayer) +{ + g_debug("[%s] Setting gstreamer pipeline state to NULL", __FUNCTION__); + + gst_element_set_state(tvplayer->gst_pipeline, GST_STATE_NULL); + + if (tvplayer->is_livetv) { + if (!gmyth_recorder_stop_livetv(tvplayer->recorder)) { + g_warning("[%s] Error while stoping remote encoder", + __FUNCTION__); + } + } +} + +/** Queries if the tvplayer is active playing A/V content. + * + * @param tvplayer the object instance. + * @return TRUE if the tvplayer is active, FALSE otherwise. + */ +gboolean +mmyth_tvplayer_is_playing(MMythTVPlayer * tvplayer) +{ + return (GST_STATE(tvplayer->gst_pipeline) == GST_STATE_PLAYING); +} + +/** Static function that sets the tvplayer state to PLAYING. + * + * @param tvplayer the object instance. + * @return TRUE if the tvplayer is active, FALSE otherwise. + */ +static gboolean +idle_play(gpointer data) +{ + MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(data); + + g_debug("MMythTVPlayer: Setting pipeline state to PLAYING\n"); + + gst_element_set_state(tvplayer->gst_pipeline, GST_STATE_PLAYING); + + return FALSE; +} + +/** Start playing A/V with the tvplayer attributes. + * + * @param tvplayer the object instance. + */ +void +mmyth_tvplayer_start_playing(MMythTVPlayer * tvplayer) +{ + + // FIXME: Move this to livetv_setup?? + if (tvplayer->is_livetv) { + +#if 0 + if (!tvplayer || !(tvplayer->proginfo) + || !(tvplayer->local_hostname) + || !(tvplayer->gst_source)) { + g_warning("GMythtvPlayer not ready to start playing\n"); + } + + if (!(tvplayer->proginfo->pathname)) { + g_warning + ("[%s] Playback url is null, could not play the myth content", + __FUNCTION__); + return; + } + + g_debug("MMythTVPlayer: Start playing %s", + tvplayer->proginfo->pathname->str); +#endif + g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-live", + TRUE, NULL); +#if 0 + if (tvplayer->tvchain != NULL) { + GString *str_chainid = + gmyth_tvchain_get_id(tvplayer->tvchain); + g_print("[%s]\tCHAIN ID: %s\n", __FUNCTION__, + str_chainid->str); + + g_object_set(G_OBJECT(tvplayer->gst_source), + "mythtv-live-chainid", g_strdup(str_chainid->str), + NULL); + if (str_chainid != NULL) + g_string_free(str_chainid, FALSE); + } + + if (tvplayer->recorder != NULL) + g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-live-id", + tvplayer->recorder->recorder_num, NULL); + g_debug("[%s] Setting location to %s", __FUNCTION__, + tvplayer->proginfo->pathname->str); + + /* + * Sets the gstreamer properties acording to the service access + * address + */ + g_object_set(G_OBJECT(tvplayer->gst_source), "location", + tvplayer->proginfo->pathname->str, NULL); +#endif + } + + g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-version", + MYTHTV_VERSION_DEFAULT, NULL); + + g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-debug", + TRUE, NULL); + + g_idle_add(idle_play, tvplayer); + +}