/** * 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->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) { 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; #endif GstElement *audiosink; GstElement *videoqueue1, *videoqueue2, *audioqueue1, *audioqueue2; 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"); audioqueue2 = gst_element_factory_make ("queue", "audio-queue2"); #ifdef MAEMO_PLATFORM audiosink = gst_element_factory_make ("dspmp3sink", "audio-output"); #else 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 && audioqueue2 && 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; tvplayer->audioqueue2 = audioqueue2; g_object_ref (tvplayer->videoqueue1); g_object_ref (tvplayer->videoqueue2); g_object_ref (tvplayer->audioqueue1); g_object_ref (tvplayer->audioqueue2); 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, audioqueue1, audiodec, audioconv, audioqueue2, 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 (videoqueue1, videodec, videoqueue2, videocolortrs, videosink, NULL); gst_element_link_many (audioqueue1, audiodec, audioconv, audioqueue2, 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->recorder = remote_request_next_free_recorder (-1); if ( tvplayer->recorder == 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_recorder_setup(tvplayer->recorder); 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_recorder_spawntv ( tvplayer->recorder, 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->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); }