diff -r 000000000000 -r 084148043ccf maemo-ui/src/mmyth_tvplayer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maemo-ui/src/mmyth_tvplayer.c Thu Sep 28 22:04:16 2006 +0100 @@ -0,0 +1,684 @@ +/** + * 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 "gmyth_context.h" +#include "gmyth_remote_util.h" + +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->audioqueue, "sink")); + } else { + ret = gst_pad_link (pad, gst_element_get_pad (tvplayer->videoqueue, "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->videoqueue = NULL; + tvplayer->audioqueue = 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->remote_encoder = 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->remote_encoder != NULL ) + g_object_unref (tvplayer->remote_encoder); + 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_videosink != NULL ) + g_object_unref (tvplayer->gst_videosink); + if ( tvplayer->videoqueue != NULL ) + g_object_unref (tvplayer->videoqueue); + if ( tvplayer->audioqueue != NULL ) + g_object_unref (tvplayer->audioqueue); + + 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) +{ + + 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; +#ifndef MAEMO_PLATFORM + GstElement *audiodec, *audioconv; +#endif + GstElement *audiosink; + GstElement *videoqueue, *audioqueue; + + 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 ("ffdemux_nuv", "nuv-demux"); + + /* Gstreamer Video elements */ + videoqueue = gst_element_factory_make ("queue", "video-queue"); + videodec = gst_element_factory_make ("ffdec_mpeg4", "video-decoder"); +#ifdef MAEMO_PLATFORM + videosink = gst_element_factory_make ("sdlvideosink", "image-output"); +#else + videosink = gst_element_factory_make ("xvimagesink", "image-output"); +#endif + + /* Gstreamer Audio elements */ + audioqueue = gst_element_factory_make ("queue", "audio-queue"); +#ifdef MAEMO_PLATFORM + audiosink = gst_element_factory_make ("dspmp3sink", "audio-output"); +#else + audiodec = gst_element_factory_make ("ffdec_mp3", "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) || + !(videoqueue && audioqueue && 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; + + 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; + g_object_ref (tvplayer->gst_pipeline); + g_object_ref (tvplayer->gst_source); + g_object_ref (tvplayer->gst_videodec); + g_object_ref (tvplayer->gst_videosink); + + tvplayer->videoqueue = videoqueue; + tvplayer->audioqueue = audioqueue; + g_object_ref (tvplayer->videoqueue); + g_object_ref (tvplayer->audioqueue); + + 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, videoqueue, + videodec, videosink, audioqueue, audiodec, audioconv, audiosink, NULL); + + { +// 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 (videoqueue, videodec, videosink, NULL); + gst_element_link_many (audioqueue, audiodec, audioconv, audiosink, NULL); + + 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, gchar *filename) +{ + GMythSettings *msettings = gmyth_context_get_settings(); + + // FIXME: we should receive the uri instead of filename + GString *hostname = gmyth_settings_get_backend_hostname (msettings); + int port = gmyth_settings_get_backend_port(msettings); + + GString *fullpath = g_string_new ("myth://"); + g_string_append_printf (fullpath, "%s:%d/%s", hostname->str, 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) +{ + GMythSettings *msettings = gmyth_context_get_settings (); + gboolean res = TRUE; + + res = gmyth_context_check_connection(); + if (!res) { + g_warning ("[%s] LiveTV can not connect to backend", __FUNCTION__); + res = FALSE; + goto error; + } + + tvplayer->backend_hostname = gmyth_settings_get_backend_hostname(msettings); + tvplayer->backend_port = gmyth_settings_get_backend_port (msettings); + + tvplayer->local_hostname = g_string_new(""); + gmyth_context_get_local_hostname (tvplayer->local_hostname); + + if ( tvplayer->local_hostname == NULL ) { + res = FALSE; + goto error; + } + + // Gets the remote encoder num + tvplayer->remote_encoder = remote_request_next_free_recorder (-1); + + if ( tvplayer->remote_encoder == NULL ) { + g_warning ("[%s] None remote encoder available", __FUNCTION__); + res = FALSE; + goto error; + } + + // Creates livetv chain handler + tvplayer->tvchain = GMYTH_TVCHAIN ( g_object_new(GMYTH_TVCHAIN_TYPE, NULL) ); + gmyth_tvchain_initialize ( tvplayer->tvchain, tvplayer->local_hostname ); + + if ( tvplayer->tvchain == NULL || tvplayer->tvchain->tvchain_id == NULL ) { + res = FALSE; + goto error; + } + + // Init remote encoder. Opens its control socket. + res = gmyth_remote_encoder_setup(tvplayer->remote_encoder); + if ( !res ) { + g_warning ("[%s] Fail while setting remote encoder\n", __FUNCTION__); + res = FALSE; + goto error; + } + // Spawn live tv. Uses the socket to send mythprotocol data to start livetv in the backend (remotelly) + res = gmyth_remote_encoder_spawntv ( tvplayer->remote_encoder, + gmyth_tvchain_get_id(tvplayer->tvchain) ); + if (!res) { + g_warning ("[%s] Fail while spawn tv\n", __FUNCTION__); + res = FALSE; + goto error; + } + + // Reload all TV chain from Mysql database. + gmyth_tvchain_reload_all (tvplayer->tvchain); + + if ( tvplayer->tvchain == NULL ) { + res = FALSE; + goto error; + } + + // Get program info from database using chanid and starttime + tvplayer->proginfo = gmyth_tvchain_get_program_at (tvplayer->tvchain, -1); + if ( tvplayer->proginfo == NULL ) { + g_warning ("[%s] LiveTV not successfully started.\n", __FUNCTION__ ); + res = FALSE; + goto error; + } else { + g_debug ("[%s] MythLiveTV: All requests to backend to start TV were OK.\n", __FUNCTION__ ); + } + + return res; + +error: + if ( tvplayer->backend_hostname != NULL ) { + g_string_free( tvplayer->backend_hostname, TRUE ); + res = FALSE; + } + + if ( tvplayer->local_hostname != NULL ) { + g_string_free( tvplayer->local_hostname, TRUE ); + res = FALSE; + } + + if ( tvplayer->remote_encoder != NULL ) { + g_object_unref (tvplayer->remote_encoder); + tvplayer->remote_encoder = 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)), FALSE ); + 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_remote_encoder_stop_livetv (tvplayer->remote_encoder)) { + 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->remote_encoder != NULL ) + g_object_set (G_OBJECT (tvplayer->gst_source), "mythtv-live-id", + tvplayer->remote_encoder->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); + +} +