1 /*****************************************************************************
2 * intf.c: interface for CMML annotations/hyperlinks
3 *****************************************************************************
4 * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5 * Organisation (CSIRO) Australia
6 * Copyright (C) 2004 the VideoLAN team
10 * Authors: Andre Pang <Andre.Pang@csiro.au>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
34 #include <vlc_common.h>
42 #include <vlc_codec.h>
43 #include <vlc_interface.h>
44 #include <vlc_playlist.h>
49 #include "browser_open.h"
54 #undef CMML_INTF_USE_TIMED_URIS
56 #undef CMML_INTF_DEBUG
57 #undef CMML_INTF_HISTORY_DEBUG
59 /*****************************************************************************
60 * intf_sys_t: description and status of interface
61 *****************************************************************************/
65 decoder_t * p_cmml_decoder;
66 input_thread_t * p_input;
71 struct navigation_history_t
77 /*****************************************************************************
79 *****************************************************************************/
81 int OpenIntf ( vlc_object_t * );
82 void CloseIntf ( vlc_object_t * );
84 static int InitThread ( intf_thread_t * );
85 static int MouseEvent ( vlc_object_t *, char const *,
86 vlc_value_t, vlc_value_t, void * );
87 static int KeyEvent ( vlc_object_t *, char const *,
88 vlc_value_t, vlc_value_t, void * );
90 static void FollowAnchor ( intf_thread_t * );
91 static void GoBack ( intf_thread_t * );
92 static void GoForward ( intf_thread_t * );
94 static int FollowAnchorCallback ( vlc_object_t *, char const *,
95 vlc_value_t, vlc_value_t, void * );
96 static int GoBackCallback ( vlc_object_t *, char const *,
97 vlc_value_t, vlc_value_t, void * );
98 static int GoForwardCallback ( vlc_object_t *, char const *,
99 vlc_value_t, vlc_value_t, void * );
101 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
102 #ifdef CMML_INTF_USE_TIMED_URIS
103 static int GetCurrentTimeInSeconds ( input_thread_t * );
104 static char *GetTimedURIFragmentForTime ( int );
106 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
108 static int DisplayPendingAnchor ( intf_thread_t *, vout_thread_t * );
109 static history_t * GetHistory ( playlist_t * );
110 static void ReplacePlaylistItem ( playlist_t *, char * );
112 /* Exported functions */
113 static void RunIntf ( intf_thread_t *p_intf );
115 /*****************************************************************************
116 * OpenIntf: initialize CMML interface
117 *****************************************************************************/
118 int OpenIntf ( vlc_object_t *p_this )
120 intf_thread_t *p_intf = (intf_thread_t *)p_this;
122 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
123 if( p_intf->p_sys == NULL )
126 p_intf->pf_run = RunIntf;
127 vlc_mutex_init( &p_intf->p_sys->lock );
129 var_AddCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
130 /* we also need to add the callback for "mouse-clicked", but do that later
131 * when we've found a p_vout */
133 var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
134 var_AddCallback( p_intf->p_libvlc, "browse-go-back",
135 GoBackCallback, p_intf );
136 var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
137 var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
138 GoForwardCallback, p_intf );
139 var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
140 var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
141 FollowAnchorCallback, p_intf );
146 /*****************************************************************************
147 * CloseIntf: destroy dummy interface
148 *****************************************************************************/
149 void CloseIntf ( vlc_object_t *p_this )
151 intf_thread_t * p_intf = (intf_thread_t *)p_this;
152 vout_thread_t * p_vout;
154 #ifdef CMML_INTF_DEBUG
155 msg_Dbg( p_intf, "freeing CMML interface" );
158 /* erase the anchor text description from the video output if it exists */
159 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
162 /* enable CMML as a subtitle track */
163 spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
164 vlc_object_release( p_vout );
167 var_DelCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
169 vlc_object_release( p_intf->p_sys->p_cmml_decoder );
171 vlc_mutex_destroy( &p_intf->p_sys->lock );
172 free( p_intf->p_sys );
176 /*****************************************************************************
178 *****************************************************************************/
179 static void RunIntf( intf_thread_t *p_intf )
181 int canc = vlc_savecancel();
182 vout_thread_t * p_vout = NULL;
184 if( InitThread( p_intf ) < 0 )
186 msg_Err( p_intf, "can't initialize CMML interface" );
189 #ifdef CMML_INTF_DEBUG
190 msg_Dbg( p_intf, "CMML intf initialized" );
194 while( vlc_object_alive (p_intf) )
196 /* if video output is dying, disassociate ourselves from it */
197 if( p_vout && !vlc_object_alive (p_vout) )
199 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
200 vlc_object_release( p_vout );
204 /* find a video output if we currently don't have one */
207 p_vout = vlc_object_find( p_intf->p_sys->p_input,
208 VLC_OBJECT_VOUT, FIND_CHILD );
211 #ifdef CMML_INTF_DEBUG
212 msg_Dbg( p_intf, "found vout thread" );
214 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
218 vlc_mutex_lock( &p_intf->p_sys->lock );
223 switch( p_intf->p_sys->i_key_action )
225 case ACTIONID_NAV_ACTIVATE:
226 FollowAnchor( p_intf );
228 case ACTIONID_HISTORY_BACK:
231 case ACTIONID_HISTORY_FORWARD:
237 p_intf->p_sys->i_key_action = 0;
238 vlc_mutex_unlock( &p_intf->p_sys->lock );
240 (void) DisplayPendingAnchor( p_intf, p_vout );
243 msleep( INTF_IDLE_SLEEP );
246 /* if we're here, the video output is dying: release the vout object */
250 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
251 vlc_object_release( p_vout );
254 vlc_object_release( p_intf->p_sys->p_input );
255 vlc_restorecancel( canc );
258 /*****************************************************************************
259 * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
260 * decoder and display it on screen
261 *****************************************************************************/
262 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
264 decoder_t *p_cmml_decoder;
265 char *psz_description = NULL;
266 char *psz_url = NULL;
268 intf_thread_t *p_primary_intf;
271 p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
272 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
281 psz_description = val.p_address;
283 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
286 psz_url = val.p_address;
291 /* display anchor as subtitle on-screen */
292 if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
295 /* text render unsuccessful: do nothing */
299 /* text render successful: clear description */
300 val.p_address = NULL;
301 if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
305 "reset of psz-current-anchor-description failed" );
307 free( psz_description );
315 /*****************************************************************************
317 *****************************************************************************/
318 static int InitThread( intf_thread_t * p_intf )
320 /* We might need some locking here */
321 if( vlc_object_alive (p_intf) )
323 input_thread_t * p_input;
324 decoder_t *p_cmml_decoder;
326 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
327 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
329 #ifdef CMML_INTF_DEBUG
330 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
331 p_cmml_decoder, p_input );
334 /* Maybe the input just died */
335 if( p_input == NULL )
340 vlc_mutex_lock( &p_intf->p_sys->lock );
342 p_intf->p_sys->p_input = p_input;
343 p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
345 p_intf->p_sys->i_key_action = 0;
347 vlc_mutex_unlock( &p_intf->p_sys->lock );
357 /*****************************************************************************
358 * MouseEvent: callback for mouse events
359 *****************************************************************************/
360 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
361 vlc_value_t oldval, vlc_value_t newval, void *p_data )
363 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
364 VLC_UNUSED(oldval); VLC_UNUSED(newval);
366 /* TODO: handle mouse clicks on the anchor text */
371 /*****************************************************************************
372 * KeyEvent: callback for keyboard events
373 *****************************************************************************/
374 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
375 vlc_value_t oldval, vlc_value_t newval, void *p_data )
377 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
378 VLC_UNUSED(oldval); VLC_UNUSED(newval);
379 intf_thread_t *p_intf = (intf_thread_t *)p_data;
382 vlc_mutex_lock( &p_intf->p_sys->lock );
383 /* FIXME: key presses might get lost here... */
384 p_intf->p_sys->i_key_action = newval.i_int;
386 vlc_mutex_unlock( &p_intf->p_sys->lock );
391 /*****************************************************************************
392 * FollowAnchor: follow the current anchor being displayed to the user
393 *****************************************************************************/
394 static void FollowAnchor ( intf_thread_t *p_intf )
397 decoder_t *p_cmml_decoder;
398 char *psz_url = NULL;
401 msg_Dbg( p_intf, "User followed anchor" );
403 p_sys = p_intf->p_sys;
404 p_cmml_decoder = p_sys->p_cmml_decoder;
406 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
409 if( val.p_address ) psz_url = val.p_address;
412 #ifdef CMML_INTF_DEBUG
413 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
418 playlist_t *p_playlist;
419 playlist_item_t *p_current_item;
420 char *psz_uri_to_load;
424 p_playlist = pl_Hold( p_intf );
427 p_current_item = playlist_CurrentPlayingItem( p_playlist );
428 char *psz_uri = input_item_GetURI( p_current_item->p_input );
429 #ifdef CMML_INTF_DEBUG
430 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
433 psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
436 #ifdef CMML_INTF_DEBUG
437 msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
440 if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
442 msg_Dbg( p_intf, "couldn't get time from current clip" );
445 i_seconds = time.i_time / 1000000;
446 #ifdef CMML_INTF_DEBUG
447 msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
450 /* TODO: we need a (much) more robust way of detecting whether
451 * the file's a media file ... */
452 if( strstr( psz_uri_to_load, ".anx" ) != NULL )
454 history_t *p_history = NULL;
455 history_item_t *p_history_item = NULL;
458 p_history = GetHistory( p_playlist );
460 /* create history item */
461 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
462 p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
463 free( psz_timed_url );
465 if( !p_history_item )
467 msg_Warn( p_intf, "could not initialise history item" );
471 #ifdef CMML_INTF_DEBUG
472 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
474 history_PruneAndInsert( p_history, p_history_item );
475 #ifdef CMML_INTF_DEBUG
476 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
477 p_history_item, p_history_item->psz_uri );
478 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
482 /* free current-anchor-url */
484 val.p_address = NULL;
485 if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
488 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
491 ReplacePlaylistItem( p_playlist, psz_uri_to_load );
495 #ifdef CMML_INTF_DEBUG
496 msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
498 (void) browser_Open( psz_url );
499 playlist_Control( p_playlist, PLAYLIST_PAUSE, pl_Unlocked, 0 );
502 free( psz_uri_to_load );
504 vlc_object_release( p_playlist );
509 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
510 playlist_item_t *p_current_item )
512 #ifdef CMML_INTF_USE_TIMED_URIS
513 char *psz_url = NULL;
514 char *psz_return_value = NULL;
515 char *psz_seconds = NULL;
518 char *psz_uri = input_item_GetURI( p_current_item->p_input );
519 psz_url = XURL_GetWithoutFragment( psz_uri );
522 /* Get current time as a string */
523 if( XURL_IsFileURL( psz_url ) == true )
524 psz_url = xstrcat( psz_url, "#" );
526 psz_url = xstrcat( psz_url, "?" );
528 /* jump back to 2 seconds before where we are now */
529 i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
530 psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
533 psz_url = xstrcat( psz_url, psz_seconds );
535 psz_return_value = psz_url;
538 return psz_return_value;
542 return input_item_GetURI( p_current_item->p_input );
548 #ifdef CMML_INTF_USE_TIMED_URIS
550 * Get the current time, rounded down to the nearest second
552 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
555 int GetCurrentTimeInSeconds( input_thread_t *p_input )
560 var_Get( p_input, "time", &time );
561 i_seconds = time.i_time / 1000000;
566 char *GetTimedURIFragmentForTime( int seconds )
570 if( asprintf( &psz_time, "%d", seconds ) == -1 )
577 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
578 vlc_value_t oldval, vlc_value_t newval, void *p_data )
580 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
581 VLC_UNUSED(oldval); VLC_UNUSED(newval);
582 intf_thread_t *p_intf = (intf_thread_t *) p_data;
588 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
589 vlc_value_t oldval, vlc_value_t newval, void *p_data )
591 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
592 VLC_UNUSED(oldval); VLC_UNUSED(newval);
593 intf_thread_t *p_intf = (intf_thread_t *) p_data;
599 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
600 vlc_value_t oldval, vlc_value_t newval,
603 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
604 VLC_UNUSED(oldval); VLC_UNUSED(newval);
605 intf_thread_t *p_intf = (intf_thread_t *) p_data;
606 FollowAnchor( p_intf );
611 void GoBack( intf_thread_t *p_intf )
614 history_t *p_history = NULL;
615 history_item_t *p_history_item = NULL;
616 history_item_t *p_new_history_item = NULL;
617 playlist_t *p_playlist = NULL;
618 char *psz_timed_url = NULL;
619 playlist_item_t *p_current_item;
621 #ifdef CMML_INTF_DEBUG
622 msg_Dbg( p_intf, "Going back in navigation history" );
625 /* Find the playlist */
626 p_playlist = pl_Hold( p_intf );
628 /* Retrieve navigation history from playlist */
629 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
632 /* History doesn't exist yet: ignore user's request */
633 msg_Warn( p_intf, "can't go back: no history exists yet" );
634 vlc_object_release( p_playlist );
638 p_history = history.p_address;
639 #ifdef CMML_INTF_DEBUG
640 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
641 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
642 p_history->p_xarray );
645 /* Check whether we can go back in the history */
646 if( history_CanGoBack( p_history ) == false )
648 msg_Warn( p_intf, "can't go back: already at beginning of history" );
649 vlc_object_release( p_playlist );
653 p_current_item = playlist_CurrentPlayingItem( p_playlist );
655 /* Save the currently-playing media in a new history item */
656 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
657 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
658 free( psz_timed_url );
660 if( !p_new_history_item )
662 #ifdef CMML_INTF_DEBUG
663 msg_Dbg( p_intf, "back: could not initialise new history item" );
665 vlc_object_release( p_playlist );
669 /* Go back in the history, saving the currently-playing item */
670 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
671 p_history_item = history_Item( p_history );
673 #ifdef CMML_INTF_DEBUG
674 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
675 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
676 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
679 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
680 vlc_object_release( p_playlist );
684 void GoForward( intf_thread_t *p_intf )
687 history_t *p_history = NULL;
688 history_item_t *p_history_item = NULL;
689 history_item_t *p_new_history_item = NULL;
690 playlist_t *p_playlist = NULL;
691 playlist_item_t *p_current_item;
693 #ifdef CMML_INTF_DEBUG
694 msg_Dbg( p_intf, "Going forward in navigation history" );
697 /* Find the playlist */
698 p_playlist = pl_Hold( p_intf );
700 /* Retrieve navigation history from playlist */
701 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
704 /* History doesn't exist yet: ignore user's request */
705 msg_Warn( p_intf, "can't go back: no history exists yet" );
706 vlc_object_release( p_playlist );
710 p_history = history.p_address;
711 #ifdef CMML_INTF_DEBUG
712 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
713 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
714 p_history->p_xarray );
717 /* Check whether we can go forward in the history */
718 if( history_CanGoForward( p_history ) == false )
720 msg_Warn( p_intf, "can't go forward: already at end of history" );
721 vlc_object_release( p_playlist );
725 /* Save the currently-playing media in a new history item */
726 p_new_history_item = malloc( sizeof(history_item_t) );
727 if( !p_new_history_item )
729 #ifdef CMML_INTF_DEBUG
730 msg_Dbg( p_intf, "forward: could not initialise new history item" );
732 vlc_object_release( p_playlist );
735 p_current_item = playlist_CurrentPlayingItem( p_playlist );
736 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
738 p_new_history_item->psz_name = p_new_history_item->psz_uri;
740 /* Go forward in the history, saving the currently-playing item */
741 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
742 p_history_item = history_Item( p_history );
744 #ifdef CMML_INTF_DEBUG
745 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
746 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
747 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
750 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
751 vlc_object_release( p_playlist );
754 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
756 playlist_Stop( p_playlist );
757 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
758 PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, true /* FIXME: p_playlist->status.i_index */,
762 /****************************************************************************
763 * DisplayAnchor: displays an anchor on the given video output
764 ****************************************************************************/
765 static int DisplayAnchor( intf_thread_t *p_intf,
766 vout_thread_t *p_vout,
767 char *psz_anchor_description,
768 char *psz_anchor_url )
770 int i_margin_h, i_margin_v;
782 /* Should display subtitle underlined and in blue, but it looks
783 * like VLC doesn't implement any text styles yet. D'oh! */
784 // p_style = &blue_with_underline;
788 /* TODO: p_subpicture doesn't have the proper i_x and i_y
789 * coordinates. Need to look at the subpicture display system to
791 if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
792 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
793 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
795 /* Displayed successfully */
804 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
811 static history_t * GetHistory( playlist_t *p_playlist )
814 history_t *p_history = NULL;
816 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
818 /* history doesn't exist yet: need to create it */
819 history_t *new_history = history_New();
820 val.p_address = new_history;
821 var_Create( p_playlist, "navigation-history",
822 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
823 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
825 msg_Warn( p_playlist, "could not initialise history" );
829 p_history = new_history;
830 #ifdef CMML_INTF_HISTORY_DEBUG
831 msg_Dbg( p_playlist, "nav history created at %p", new_history );
832 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
833 p_history->i_index, p_history->p_xarray );
839 p_history = val.p_address;
840 #ifdef CMML_INTF_HISTORY_DEBUG
841 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );