rosfran@68: /** rosfran@68: * GMyth Library rosfran@68: * rosfran@68: * @file gmyth/gmyth_livetv.c rosfran@68: * rosfran@68: * @brief

GMythLiveTV starts a remote TV session with the MythTV backend. rosfran@68: * rosfran@68: * Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia. rosfran@68: * @author Rosfran Lins Borges rosfran@68: * rosfran@68: *//* rosfran@68: * rosfran@68: * This program is free software; you can redistribute it and/or modify rosfran@68: * it under the terms of the GNU Lesser General Public License as published by rosfran@68: * the Free Software Foundation; either version 2 of the License, or rosfran@68: * (at your option) any later version. rosfran@68: * rosfran@68: * This program is distributed in the hope that it will be useful, rosfran@68: * but WITHOUT ANY WARRANTY; without even the implied warranty of rosfran@68: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the rosfran@68: * GNU General Public License for more details. rosfran@68: * rosfran@68: * You should have received a copy of the GNU Lesser General Public License rosfran@68: * along with this program; if not, write to the Free Software rosfran@68: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA rosfran@68: */ leo_sobral@213: leo_sobral@213: #ifdef HAVE_CONFIG_H leo_sobral@213: #include "config.h" leo_sobral@213: #endif leo_sobral@213: rosfran@240: #include "gmyth_livetv.h" rosfran@42: #include "gmyth_remote_util.h" rosfran@42: #include "gmyth_tvchain.h" melunko@117: #include "gmyth_socket.h" renatofilho@131: #include "gmyth_debug.h" rosfran@41: rosfran@42: #include "gmyth_file_transfer.h" rosfran@216: #include "gmyth_monitor_handler.h" rosfran@41: rosfran@41: static void gmyth_livetv_class_init (GMythLiveTVClass *klass); rosfran@41: static void gmyth_livetv_init (GMythLiveTV *object); rosfran@41: rosfran@41: static void gmyth_livetv_dispose (GObject *object); rosfran@41: static void gmyth_livetv_finalize (GObject *object); rosfran@41: rosfran@41: static gint tvchain_curr_index = -1; rosfran@41: melunko@107: #define GMYTHTV_RECORDER_HEADER "QUERY_RECORDER" melunko@107: melunko@107: rosfran@41: G_DEFINE_TYPE(GMythLiveTV, gmyth_livetv, G_TYPE_OBJECT) rosfran@41: rosfran@41: static void rosfran@41: gmyth_livetv_class_init (GMythLiveTVClass *klass) rosfran@41: { rosfran@41: GObjectClass *gobject_class; rosfran@41: rosfran@41: gobject_class = (GObjectClass *) klass; rosfran@41: rosfran@41: gobject_class->dispose = gmyth_livetv_dispose; rosfran@41: gobject_class->finalize = gmyth_livetv_finalize; rosfran@41: } rosfran@41: rosfran@41: static void rosfran@41: gmyth_livetv_init (GMythLiveTV *livetv) rosfran@41: { rosfran@216: livetv->backend_info = NULL; rosfran@41: livetv->local_hostname = NULL; rosfran@216: livetv->file_transfer = NULL; rosfran@216: livetv->setup_done = FALSE; rosfran@41: rosfran@65: livetv->recorder = NULL; rosfran@41: livetv->tvchain = NULL; rosfran@41: livetv->proginfo = NULL; rosfran@41: } rosfran@41: rosfran@41: static void rosfran@41: gmyth_livetv_dispose (GObject *object) rosfran@41: { rosfran@41: G_OBJECT_CLASS (gmyth_livetv_parent_class)->dispose (object); rosfran@41: } rosfran@41: rosfran@41: static void rosfran@41: gmyth_livetv_finalize (GObject *object) rosfran@41: { rosfran@41: g_signal_handlers_destroy (object); rosfran@41: rosfran@41: GMythLiveTV *livetv = GMYTH_LIVETV (object); rosfran@41: renatofilho@131: gmyth_debug ("[%s] Finalizing livetv", __FUNCTION__); rosfran@41: rosfran@216: if ( livetv->monitor != NULL ) { rosfran@216: g_object_unref (livetv->monitor); rosfran@216: livetv->monitor = NULL; rosfran@216: } rosfran@216: rosfran@65: if ( livetv->recorder != NULL ) { rosfran@65: g_object_unref (livetv->recorder); rosfran@65: livetv->recorder = NULL; rosfran@41: } rosfran@41: rosfran@41: if ( livetv->tvchain != NULL ) { rosfran@41: g_object_unref (livetv->tvchain); rosfran@41: livetv->tvchain = NULL; rosfran@41: } rosfran@41: rosfran@41: if ( livetv->proginfo != NULL ) { rosfran@41: g_object_unref (livetv->proginfo); rosfran@41: livetv->proginfo = NULL; rosfran@41: } rosfran@216: rosfran@216: if ( livetv->file_transfer != NULL ) { rosfran@216: g_object_unref (livetv->file_transfer); rosfran@216: livetv->file_transfer = NULL; rosfran@216: } rosfran@216: rosfran@216: if ( livetv->backend_info != NULL ) { rosfran@216: g_object_unref (livetv->backend_info); rosfran@216: livetv->backend_info = NULL; rosfran@216: } rosfran@41: rosfran@41: G_OBJECT_CLASS ( gmyth_livetv_parent_class )->finalize ( object ); rosfran@41: } rosfran@41: rosfran@41: GMythLiveTV* rosfran@41: gmyth_livetv_new () rosfran@41: { rosfran@41: GMythLiveTV *livetv = GMYTH_LIVETV ( g_object_new( GMYTH_LIVETV_TYPE, NULL ) ); rosfran@41: rosfran@41: return livetv; rosfran@41: } rosfran@41: rosfran@212: static void rosfran@212: gmyth_livetv_monitor_signal_handler( GMythMonitorHandler *monitor, gint msg_code, rosfran@232: gchar* message, gpointer user_data ) rosfran@212: { rosfran@233: GMythLiveTV *live_tv = GMYTH_LIVETV ( user_data ); rosfran@233: //g_object_ref( live_tv ); rosfran@216: rosfran@232: gmyth_debug( "LIVETV Signal handler ( msg = %s, code = %d, live_tv param = %s, user_data = %s )\n", message, msg_code, live_tv != NULL ? "" : rosfran@220: "NULL", user_data != NULL ? "" : "NULL" ); rosfran@216: rosfran@232: if ( NULL == live_tv ) rosfran@232: { rosfran@232: gmyth_debug( "LiveTV_obj is equals to NULL!!!" ); rosfran@232: return; rosfran@232: } rosfran@216: rosfran@216: switch ( msg_code ) rosfran@216: { rosfran@216: rosfran@216: case GMYTH_BACKEND_PROGRAM_INFO_CHANGED: rosfran@216: { rosfran@234: gmyth_debug( "LIVETV Program Changed request received [ msg = %s ]. Watching if the new "\ rosfran@234: "TV Chain ID is the same as the old one...\n", message ); rosfran@234: if ( g_ascii_strcasecmp ( message, (gmyth_tvchain_get_id( live_tv->tvchain ))->str ) != 0 ) { rosfran@234: gmyth_debug( "OK!!! MOVED to the next program chain [actual == %s]!", rosfran@234: (gmyth_tvchain_get_id( live_tv->tvchain ))->str ); rosfran@234: /* advertises the FileTransfer about the program info changed */ rosfran@234: if ( live_tv->file_transfer != NULL ) rosfran@234: { rosfran@234: gmyth_debug( "Emitting signal to the FileTransfer... [ \"program-info-changed \" ]" ); rosfran@234: rosfran@234: gmyth_file_transfer_emit_program_info_changed_signal( live_tv->file_transfer, rosfran@234: msg_code, (gpointer)live_tv ); rosfran@234: rosfran@234: //gmyth_livetv_monitor_handler_stop( live_tv ); rosfran@234: } else rosfran@234: gmyth_debug( "LIVETV file_transfer is NULL!!! Cannot move to the next program chain event received.\n"); rosfran@234: } rosfran@216: break; rosfran@216: } rosfran@216: default: rosfran@220: break; rosfran@220: } /* switch (Monitor Handler messages) */ rosfran@220: rosfran@220: } rosfran@220: rosfran@220: gboolean rosfran@220: gmyth_livetv_monitor_handler_start( GMythLiveTV *livetv ) rosfran@220: { rosfran@220: gboolean res = TRUE; rosfran@220: rosfran@220: if ( livetv->monitor != NULL ) rosfran@220: { rosfran@220: g_object_unref( livetv->monitor ); rosfran@220: livetv->monitor = NULL; rosfran@216: } rosfran@216: rosfran@220: livetv->monitor = gmyth_monitor_handler_new ( ); rosfran@220: rosfran@220: res = gmyth_monitor_handler_open (livetv->monitor, livetv->backend_info->hostname, rosfran@220: livetv->backend_info->port ); rosfran@220: rosfran@220: if ( res == TRUE ) rosfran@220: { rosfran@220: gmyth_debug("Connect MythTV Monitor event socket! Trying to start the message handler..."); rosfran@220: rosfran@220: res = gmyth_monitor_handler_start ( livetv->monitor ); rosfran@220: rosfran@220: if (res) rosfran@220: { rosfran@220: gmyth_debug("MythTV Monitor event socket connected and listening!"); rosfran@220: g_signal_connect ( G_OBJECT (livetv->monitor), "backend-events-handler", rosfran@220: (GCallback)gmyth_livetv_monitor_signal_handler, rosfran@220: livetv ); rosfran@220: } rosfran@220: else rosfran@220: { rosfran@220: gmyth_debug("Problems when trying to start MythTV Monitor event socket!"); rosfran@220: goto error; rosfran@220: } rosfran@220: } rosfran@220: rosfran@220: error: rosfran@220: return res; rosfran@220: rosfran@212: } rosfran@212: rosfran@220: void rosfran@220: gmyth_livetv_monitor_handler_stop( GMythLiveTV *livetv ) rosfran@220: { rosfran@220: rosfran@220: if ( livetv->monitor != NULL ) rosfran@220: { rosfran@220: g_object_unref( livetv->monitor ); rosfran@220: livetv->monitor = NULL; rosfran@220: } rosfran@220: rosfran@220: } rosfran@220: rosfran@237: static gboolean rosfran@237: gmyth_livetv_setup_recorder ( GMythLiveTV *livetv, gint channel, GMythBackendInfo *backend_info ) rosfran@41: { rosfran@41: gboolean res = TRUE; rosfran@41: melunko@117: GMythSocket *socket = gmyth_socket_new (); rosfran@216: rosfran@216: livetv->backend_info = backend_info; rosfran@41: melunko@117: // FIME: Implement this at gmyth_socket rosfran@216: res = gmyth_socket_connect_to_backend (socket, livetv->backend_info->hostname, rosfran@216: livetv->backend_info->port, TRUE); rosfran@41: if (!res) { rosfran@41: g_warning ("[%s] LiveTV can not connect to backend", __FUNCTION__); rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: melunko@117: livetv->is_livetv = TRUE; rosfran@203: rosfran@229: livetv->local_hostname = gmyth_socket_get_local_hostname (); rosfran@41: rosfran@41: if ( livetv->local_hostname == NULL ) { rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: rosfran@65: // Gets the recorder num melunko@117: livetv->recorder = remote_request_next_free_recorder (socket, -1); melunko@117: gmyth_socket_close_connection (socket); rosfran@41: rosfran@65: if ( livetv->recorder == NULL ) { rosfran@41: g_warning ("[%s] None remote encoder available", __FUNCTION__); rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: rosfran@41: // Creates livetv chain handler rosfran@41: livetv->tvchain = GMYTH_TVCHAIN ( g_object_new(GMYTH_TVCHAIN_TYPE, NULL) ); rosfran@216: gmyth_tvchain_initialize ( livetv->tvchain, livetv->backend_info ); rosfran@41: rosfran@41: if ( livetv->tvchain == NULL || livetv->tvchain->tvchain_id == NULL ) { rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: rosfran@41: // Init remote encoder. Opens its control socket. rosfran@65: res = gmyth_recorder_setup(livetv->recorder); rosfran@41: if ( !res ) { rosfran@41: g_warning ("[%s] Fail while setting remote encoder\n", __FUNCTION__); rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@237: rosfran@237: if ( channel != -1 ) { rosfran@237: gint ch = channel; rosfran@237: gint ch_idx = 0; rosfran@237: for ( ; ch_idx < 5; ch_idx++ ) { rosfran@237: if ( gmyth_recorder_check_channel( livetv->recorder, ch ) ) { rosfran@237: if ( gmyth_recorder_set_channel( livetv->recorder, ch ) ) { rosfran@237: g_print( "[%s] Channel changed!!! [%d].\n", __FUNCTION__, ch ); rosfran@237: break; rosfran@237: } rosfran@67: } rosfran@237: ++ch; rosfran@237: } rosfran@237: } rosfran@67: rosfran@41: // Spawn live tv. Uses the socket to send mythprotocol data to start livetv in the backend (remotelly) rosfran@65: res = gmyth_recorder_spawntv ( livetv->recorder, rosfran@41: gmyth_tvchain_get_id(livetv->tvchain) ); rosfran@41: if (!res) { rosfran@41: g_warning ("[%s] Fail while spawn tv\n", __FUNCTION__); rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: rosfran@41: // Reload all TV chain from Mysql database. rosfran@41: gmyth_tvchain_reload_all (livetv->tvchain); rosfran@41: rosfran@41: if ( livetv->tvchain == NULL ) { rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } rosfran@41: rosfran@41: // Get program info from database using chanid and starttime rosfran@41: livetv->proginfo = gmyth_tvchain_get_program_at (livetv->tvchain, tvchain_curr_index++ ); rosfran@41: if ( livetv->proginfo == NULL ) { rosfran@41: g_warning ("[%s] LiveTV not successfully started.\n", __FUNCTION__ ); rosfran@41: res = FALSE; rosfran@41: goto error; rosfran@41: } else { rosfran@229: res = TRUE; rosfran@223: gmyth_debug ("GMythLiveTV: All requests to backend to start TV were OK. [%s]\n", livetv->proginfo->pathname->str ); rosfran@41: } rosfran@216: rosfran@225: if ( !gmyth_livetv_monitor_handler_start( livetv ) ) rosfran@225: { rosfran@225: res = FALSE; rosfran@237: gmyth_debug( "LiveTV MONITOR handler error on setup!" ); rosfran@225: goto error; rosfran@225: } rosfran@225: rosfran@216: livetv->setup_done = TRUE; rosfran@216: rosfran@41: return res; rosfran@41: rosfran@41: error: rosfran@54: g_print( "[%s] ERROR running LiveTV setup.\n", __FUNCTION__ ); rosfran@54: rosfran@54: if ( livetv->local_hostname != NULL ) { rosfran@229: g_string_free( livetv->local_hostname, FALSE ); rosfran@54: res = FALSE; rosfran@54: } rosfran@54: rosfran@65: if ( livetv->recorder != NULL ) { rosfran@65: g_object_unref (livetv->recorder); rosfran@65: livetv->recorder = NULL; rosfran@54: } rosfran@54: rosfran@54: if ( livetv->tvchain != NULL ) { rosfran@54: g_object_unref (livetv->tvchain); rosfran@54: livetv->tvchain = NULL; rosfran@54: } rosfran@54: rosfran@54: if ( livetv->proginfo != NULL ) { rosfran@54: g_object_unref (livetv->proginfo); rosfran@54: livetv->proginfo = NULL; rosfran@54: } rosfran@54: rosfran@220: if ( livetv->monitor != NULL ) { rosfran@220: g_object_unref (livetv->monitor); rosfran@220: livetv->monitor = NULL; rosfran@220: } rosfran@220: rosfran@54: return res; rosfran@54: rosfran@54: } rosfran@54: rosfran@54: gboolean rosfran@237: gmyth_livetv_channel_setup ( GMythLiveTV *livetv, gint channel, GMythBackendInfo *backend_info ) rosfran@237: { rosfran@237: return gmyth_livetv_setup_recorder ( livetv, channel, backend_info ); rosfran@237: } rosfran@237: rosfran@237: gboolean rosfran@237: gmyth_livetv_setup ( GMythLiveTV *livetv, GMythBackendInfo *backend_info ) rosfran@237: { rosfran@237: return gmyth_livetv_setup_recorder ( livetv, -1, backend_info ); rosfran@237: } rosfran@237: rosfran@237: gboolean rosfran@54: gmyth_livetv_next_program_chain ( GMythLiveTV *livetv ) rosfran@54: { rosfran@54: gboolean res = TRUE; rosfran@54: rosfran@216: if ( !livetv->setup_done ) rosfran@216: { rosfran@216: gmyth_debug ( "Call the setup function first!" ); rosfran@216: res= FALSE; rosfran@216: goto error; rosfran@216: } rosfran@216: rosfran@220: //if ( !gmyth_livetv_monitor_handler_start( livetv ) ) rosfran@220: // goto error; rosfran@220: rosfran@216: /* Reload all TV chain from Mysql database. */ rosfran@54: gmyth_tvchain_reload_all (livetv->tvchain); rosfran@54: rosfran@54: if ( livetv->tvchain == NULL ) { rosfran@54: res = FALSE; rosfran@54: goto error; rosfran@54: } rosfran@54: rosfran@54: // Get program info from database using chanid and starttime rosfran@54: livetv->proginfo = gmyth_tvchain_get_program_at (livetv->tvchain, -1 ); rosfran@54: if ( livetv->proginfo == NULL ) { rosfran@54: g_warning ("[%s] LiveTV not successfully started on the next program chain.\n", __FUNCTION__ ); rosfran@54: res = FALSE; rosfran@54: goto error; rosfran@54: } else { rosfran@229: res = TRUE; renatofilho@131: gmyth_debug ("[%s] GMythLiveTV: All requests to backend to start TV were OK, TV chain changed.\n", __FUNCTION__ ); rosfran@54: } rosfran@220: rosfran@220: livetv->setup_done = TRUE; rosfran@54: rosfran@54: return res; rosfran@54: rosfran@54: error: rosfran@54: g_print( "[%s] ERROR running LiveTV setup.\n", __FUNCTION__ ); rosfran@41: rosfran@41: if ( livetv->local_hostname != NULL ) { rosfran@229: g_string_free( livetv->local_hostname, FALSE ); rosfran@41: res = FALSE; rosfran@41: } rosfran@41: rosfran@65: if ( livetv->recorder != NULL ) { rosfran@65: g_object_unref (livetv->recorder); rosfran@65: livetv->recorder = NULL; rosfran@41: } rosfran@41: rosfran@41: if ( livetv->tvchain != NULL ) { rosfran@41: g_object_unref (livetv->tvchain); rosfran@41: livetv->tvchain = NULL; rosfran@41: } rosfran@41: rosfran@41: if ( livetv->proginfo != NULL ) { rosfran@41: g_object_unref (livetv->proginfo); rosfran@41: livetv->proginfo = NULL; rosfran@41: } rosfran@41: rosfran@41: return res; rosfran@41: rosfran@41: } rosfran@41: rosfran@216: GMythFileTransfer * rosfran@216: gmyth_livetv_create_file_transfer( GMythLiveTV *livetv ) rosfran@216: { rosfran@216: GMythURI* uri = NULL; rosfran@216: rosfran@216: if ( NULL == livetv || NULL == livetv->proginfo ) rosfran@216: goto done; rosfran@216: rosfran@216: if ( !livetv->setup_done ) rosfran@216: { rosfran@216: gmyth_debug( "Error: You must do the LiveTV setup, just before generating the FileTransfer from LiveTV source!" ); rosfran@216: goto done; rosfran@216: } rosfran@216: rosfran@216: gmyth_debug( "URI path = %s.\n", livetv->proginfo->pathname->str ); rosfran@216: rosfran@216: livetv->file_transfer = gmyth_file_transfer_new( livetv->backend_info ); rosfran@216: rosfran@216: if ( NULL == livetv->file_transfer ) rosfran@216: { rosfran@216: gmyth_debug( "Error: couldn't create the FileTransfer from LiveTV source!" ); rosfran@216: goto done; rosfran@216: } rosfran@216: rosfran@216: uri = gmyth_uri_new_with_value( livetv->proginfo->pathname->str ); rosfran@216: if ( NULL == uri ) rosfran@229: { rosfran@229: gmyth_debug( "Couldn't parse the URI to start LiveTV! [ uri = %s ]", livetv->proginfo->pathname->str ); rosfran@229: goto done; rosfran@216: } rosfran@216: rosfran@216: if ( !gmyth_file_transfer_open( livetv->file_transfer, uri != NULL ? gmyth_uri_get_path(uri) : rosfran@216: livetv->proginfo->pathname->str ) ) rosfran@216: { rosfran@216: gmyth_debug( "Error: couldn't open the FileTransfer from LiveTV source!" ); rosfran@216: g_object_unref( livetv->file_transfer ); rosfran@216: livetv->file_transfer = NULL; rosfran@216: goto done; rosfran@216: } rosfran@216: rosfran@216: done: rosfran@216: if ( uri != NULL ) rosfran@216: { rosfran@216: g_object_unref( uri ); rosfran@216: uri = NULL; rosfran@216: } rosfran@216: rosfran@216: return livetv->file_transfer; rosfran@216: rosfran@216: } rosfran@216: rosfran@216: /* FIXME: How to proceed differently between livetv and recorded content */ rosfran@41: void rosfran@41: gmyth_livetv_stop_playing (GMythLiveTV *livetv) rosfran@41: { renatofilho@131: gmyth_debug ("[%s] Stopping the LiveTV...\n", __FUNCTION__); rosfran@41: rosfran@41: if (livetv->is_livetv) { rosfran@220: if ( !gmyth_recorder_stop_livetv (livetv->recorder) ) { rosfran@41: g_warning ("[%s] Error while stoping remote encoder", __FUNCTION__); rosfran@41: } rosfran@41: } rosfran@41: } rosfran@41: melunko@117: // FIXME: put here just a wrapper function to call gmyth_recorder_is_recording()... rosfran@41: gboolean melunko@107: gmyth_livetv_is_recording ( GMythLiveTV *livetv ) melunko@107: { melunko@107: gboolean ret = TRUE; melunko@107: melunko@107: GMythStringList *str_list = gmyth_string_list_new (); melunko@107: GString *message = g_string_new (""); melunko@107: renatofilho@131: gmyth_debug ( "[%s]\n", __FUNCTION__ ); melunko@107: //g_static_mutex_lock (&mutex); melunko@107: // melunko@107: g_string_printf( message, "%s %d", GMYTHTV_RECORDER_HEADER, melunko@107: /* FIXME file_transfer->rec_id >= 0 ? file_transfer->rec_id : file_transfer->card_id );*/ melunko@107: livetv->recorder->recorder_num); melunko@107: gmyth_string_list_append_string (str_list, message); melunko@107: gmyth_string_list_append_string (str_list, g_string_new ("IS_RECORDING")); melunko@107: melunko@107: gmyth_socket_sendreceive_stringlist ( livetv->recorder->myth_socket, str_list ); melunko@107: melunko@107: if ( str_list != NULL && gmyth_string_list_length(str_list) > 0 ) melunko@107: { melunko@107: GString *str = NULL; melunko@107: if ( ( str = gmyth_string_list_get_string( str_list, 0 ) ) != NULL && strcmp( str->str, "bad" )!= 0 ) { melunko@107: gint is_rec = gmyth_string_list_get_int( str_list, 0 ); melunko@107: if ( is_rec != 0 ) melunko@107: ret = TRUE; melunko@107: else melunko@107: ret = FALSE; melunko@107: } melunko@107: } melunko@107: g_print( "[%s] %s, stream is %s being recorded!\n", __FUNCTION__, ret ? "YES" : "NO", ret ? "" : "NOT" ); melunko@107: //g_static_mutex_unlock (&mutex); melunko@107: melunko@107: if ( str_list != NULL ) melunko@107: g_object_unref (str_list); melunko@107: melunko@107: return ret; melunko@107: melunko@107: } melunko@107: melunko@107: melunko@107: gboolean rosfran@41: gmyth_livetv_is_playing (GMythLiveTV *livetv) rosfran@41: { rosfran@41: return TRUE; rosfran@41: } rosfran@41: rosfran@41: void rosfran@41: gmyth_livetv_start_playing (GMythLiveTV *livetv) rosfran@41: { rosfran@41: rosfran@41: // TODO rosfran@41: rosfran@41: } rosfran@41: