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_playlist.h>
48 #include "browser_open.h"
53 #undef CMML_INTF_USE_TIMED_URIS
55 #undef CMML_INTF_DEBUG
56 #undef CMML_INTF_HISTORY_DEBUG
58 /*****************************************************************************
59 * intf_sys_t: description and status of interface
60 *****************************************************************************/
61 typedef struct decoder_sys_t
66 decoder_t * p_cmml_decoder;
67 input_thread_t * p_input;
72 struct navigation_history_t
78 /*****************************************************************************
80 *****************************************************************************/
82 decoder_sys_t *OpenIntf ( vlc_object_t * );
83 void CloseIntf ( decoder_sys_t * );
85 static int InitThread ( intf_thread_t * );
86 static int MouseEvent ( vlc_object_t *, char const *,
87 vlc_value_t, vlc_value_t, void * );
88 static int KeyEvent ( vlc_object_t *, char const *,
89 vlc_value_t, vlc_value_t, void * );
91 static void FollowAnchor ( intf_thread_t * );
92 static void GoBack ( intf_thread_t * );
93 static void GoForward ( intf_thread_t * );
95 static int FollowAnchorCallback ( vlc_object_t *, char const *,
96 vlc_value_t, vlc_value_t, void * );
97 static int GoBackCallback ( vlc_object_t *, char const *,
98 vlc_value_t, vlc_value_t, void * );
99 static int GoForwardCallback ( vlc_object_t *, char const *,
100 vlc_value_t, vlc_value_t, void * );
102 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
103 #ifdef CMML_INTF_USE_TIMED_URIS
104 static int GetCurrentTimeInSeconds ( input_thread_t * );
105 static char *GetTimedURIFragmentForTime ( int );
107 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
109 static int DisplayPendingAnchor ( intf_thread_t *, vout_thread_t * );
110 static history_t * GetHistory ( playlist_t * );
111 static void ReplacePlaylistItem ( playlist_t *, char * );
113 static void *RunIntf ( vlc_object_t * );
115 /*****************************************************************************
116 * OpenIntf: initialize CMML interface
117 *****************************************************************************/
118 decoder_sys_t *OpenIntf ( vlc_object_t *p_this )
120 decoder_sys_t *p_intf = vlc_object_create( p_this, sizeof( *p_intf ) );
124 vlc_mutex_init( &p_intf->lock );
126 var_AddCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
127 /* we also need to add the callback for "mouse-clicked", but do that later
128 * when we've found a p_vout */
130 var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
131 var_AddCallback( p_intf->p_libvlc, "browse-go-back",
132 GoBackCallback, p_intf );
133 var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
134 var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
135 GoForwardCallback, p_intf );
136 var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
137 var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
138 FollowAnchorCallback, p_intf );
140 int ret = vlc_thread_create( p_intf, "cmml", RunIntf, VLC_THREAD_PRIORITY_LOW );
149 /*****************************************************************************
150 * CloseIntf: destroy dummy interface
151 *****************************************************************************/
152 void CloseIntf ( decoder_sys_t *p_intf )
154 vout_thread_t * p_vout;
156 #ifdef CMML_INTF_DEBUG
157 msg_Dbg( p_intf, "freeing CMML interface" );
160 /* erase the anchor text description from the video output if it exists */
161 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
164 /* enable CMML as a subtitle track */
165 spu_Control( vout_GetSpu( p_vout ), SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
166 vlc_object_release( p_vout );
169 var_DelCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
170 vlc_object_kill( p_intf );
171 vlc_thread_join( p_intf );
173 vlc_object_release( p_intf->p_cmml_decoder );
175 vlc_mutex_destroy( &p_intf->lock );
176 vlc_object_release( p_intf );
180 /*****************************************************************************
182 *****************************************************************************/
183 static void *RunIntf( vlc_object_t *p_obj )
185 decoder_sys_t *p_intf = (decoder_sys_t *)p_obj;
186 int canc = vlc_savecancel();
187 vout_thread_t * p_vout = NULL;
189 if( InitThread( p_intf ) < 0 )
191 msg_Err( p_intf, "can't initialize CMML interface" );
194 #ifdef CMML_INTF_DEBUG
195 msg_Dbg( p_intf, "CMML intf initialized" );
199 while( vlc_object_alive (p_intf) )
201 /* if video output is dying, disassociate ourselves from it */
202 if( p_vout && !vlc_object_alive (p_vout) )
204 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
205 vlc_object_release( p_vout );
209 /* find a video output if we currently don't have one */
212 p_vout = vlc_object_find( p_intf->p_input,
213 VLC_OBJECT_VOUT, FIND_CHILD );
216 #ifdef CMML_INTF_DEBUG
217 msg_Dbg( p_intf, "found vout thread" );
219 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
223 vlc_mutex_lock( &p_intf->lock );
228 switch( p_intf->i_key_action )
230 case ACTIONID_NAV_ACTIVATE:
231 FollowAnchor( p_intf );
233 case ACTIONID_HISTORY_BACK:
236 case ACTIONID_HISTORY_FORWARD:
242 p_intf->i_key_action = 0;
243 vlc_mutex_unlock( &p_intf->lock );
245 (void) DisplayPendingAnchor( p_intf, p_vout );
248 msleep( INTF_IDLE_SLEEP );
251 /* if we're here, the video output is dying: release the vout object */
255 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
256 vlc_object_release( p_vout );
259 vlc_object_release( p_intf->p_input );
260 vlc_restorecancel( canc );
264 /*****************************************************************************
265 * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
266 * decoder and display it on screen
267 *****************************************************************************/
268 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
270 decoder_t *p_cmml_decoder;
271 char *psz_description = NULL;
272 char *psz_url = NULL;
276 p_cmml_decoder = p_intf->p_cmml_decoder;
277 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
286 psz_description = val.p_address;
288 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
291 psz_url = val.p_address;
296 /* display anchor as subtitle on-screen */
297 if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
300 /* text render unsuccessful: do nothing */
304 /* text render successful: clear description */
305 val.p_address = NULL;
306 if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
310 "reset of psz-current-anchor-description failed" );
312 free( psz_description );
320 /*****************************************************************************
322 *****************************************************************************/
323 static int InitThread( intf_thread_t * p_intf )
325 /* We might need some locking here */
326 if( vlc_object_alive (p_intf) )
328 input_thread_t * p_input;
329 decoder_t *p_cmml_decoder;
331 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
332 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
334 #ifdef CMML_INTF_DEBUG
335 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
336 p_cmml_decoder, p_input );
339 /* Maybe the input just died */
340 if( p_input == NULL )
345 vlc_mutex_lock( &p_intf->lock );
347 p_intf->p_input = p_input;
348 p_intf->p_cmml_decoder = p_cmml_decoder;
350 p_intf->i_key_action = 0;
352 vlc_mutex_unlock( &p_intf->lock );
362 /*****************************************************************************
363 * MouseEvent: callback for mouse events
364 *****************************************************************************/
365 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
366 vlc_value_t oldval, vlc_value_t newval, void *p_data )
368 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
369 VLC_UNUSED(oldval); VLC_UNUSED(newval);
371 /* TODO: handle mouse clicks on the anchor text */
376 /*****************************************************************************
377 * KeyEvent: callback for keyboard events
378 *****************************************************************************/
379 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
380 vlc_value_t oldval, vlc_value_t newval, void *p_data )
382 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
383 VLC_UNUSED(oldval); VLC_UNUSED(newval);
384 intf_thread_t *p_intf = (intf_thread_t *)p_data;
387 vlc_mutex_lock( &p_intf->lock );
388 /* FIXME: key presses might get lost here... */
389 p_intf->i_key_action = newval.i_int;
391 vlc_mutex_unlock( &p_intf->lock );
396 /*****************************************************************************
397 * FollowAnchor: follow the current anchor being displayed to the user
398 *****************************************************************************/
399 static void FollowAnchor ( intf_thread_t *p_intf )
401 decoder_t *p_cmml_decoder;
402 char *psz_url = NULL;
405 msg_Dbg( p_intf, "User followed anchor" );
407 p_cmml_decoder = p_intf->p_cmml_decoder;
409 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
412 if( val.p_address ) psz_url = val.p_address;
415 #ifdef CMML_INTF_DEBUG
416 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
421 playlist_t *p_playlist;
422 playlist_item_t *p_current_item;
423 char *psz_uri_to_load;
427 p_playlist = pl_Hold( p_intf );
430 p_current_item = playlist_CurrentPlayingItem( p_playlist );
431 char *psz_uri = input_item_GetURI( p_current_item->p_input );
432 #ifdef CMML_INTF_DEBUG
433 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
436 psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
439 #ifdef CMML_INTF_DEBUG
440 msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
443 if( var_Get( p_intf->p_input, "time", &time ) )
445 msg_Dbg( p_intf, "couldn't get time from current clip" );
448 i_seconds = time.i_time / 1000000;
449 #ifdef CMML_INTF_DEBUG
450 msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
453 /* TODO: we need a (much) more robust way of detecting whether
454 * the file's a media file ... */
455 if( strstr( psz_uri_to_load, ".anx" ) != NULL )
457 history_t *p_history = NULL;
458 history_item_t *p_history_item = NULL;
461 p_history = GetHistory( p_playlist );
463 /* create history item */
464 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
465 p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
466 free( psz_timed_url );
468 if( !p_history_item )
470 msg_Warn( p_intf, "could not initialise history item" );
474 #ifdef CMML_INTF_DEBUG
475 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
477 history_PruneAndInsert( p_history, p_history_item );
478 #ifdef CMML_INTF_DEBUG
479 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
480 p_history_item, p_history_item->psz_uri );
481 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
485 /* free current-anchor-url */
487 val.p_address = NULL;
488 if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
491 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
494 ReplacePlaylistItem( p_playlist, psz_uri_to_load );
498 #ifdef CMML_INTF_DEBUG
499 msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
501 (void) browser_Open( psz_url );
502 playlist_Control( p_playlist, PLAYLIST_PAUSE, pl_Unlocked, 0 );
505 free( psz_uri_to_load );
507 pl_Release( p_intf );
512 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
513 playlist_item_t *p_current_item )
515 #ifdef CMML_INTF_USE_TIMED_URIS
516 char *psz_url = NULL;
517 char *psz_return_value = NULL;
518 char *psz_seconds = NULL;
521 char *psz_uri = input_item_GetURI( p_current_item->p_input );
522 psz_url = XURL_GetWithoutFragment( psz_uri );
525 /* Get current time as a string */
526 if( XURL_IsFileURL( psz_url ) == true )
527 psz_url = xstrcat( psz_url, "#" );
529 psz_url = xstrcat( psz_url, "?" );
531 /* jump back to 2 seconds before where we are now */
532 i_seconds = GetCurrentTimeInSeconds( p_intf->p_input ) - 2;
533 psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
536 psz_url = xstrcat( psz_url, psz_seconds );
538 psz_return_value = psz_url;
541 return psz_return_value;
545 return input_item_GetURI( p_current_item->p_input );
551 #ifdef CMML_INTF_USE_TIMED_URIS
553 * Get the current time, rounded down to the nearest second
555 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
558 int GetCurrentTimeInSeconds( input_thread_t *p_input )
563 var_Get( p_input, "time", &time );
564 i_seconds = time.i_time / 1000000;
569 char *GetTimedURIFragmentForTime( int seconds )
573 if( asprintf( &psz_time, "%d", seconds ) == -1 )
580 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
581 vlc_value_t oldval, vlc_value_t newval, void *p_data )
583 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
584 VLC_UNUSED(oldval); VLC_UNUSED(newval);
585 intf_thread_t *p_intf = (intf_thread_t *) p_data;
591 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
592 vlc_value_t oldval, vlc_value_t newval, void *p_data )
594 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
595 VLC_UNUSED(oldval); VLC_UNUSED(newval);
596 intf_thread_t *p_intf = (intf_thread_t *) p_data;
602 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
603 vlc_value_t oldval, vlc_value_t newval,
606 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
607 VLC_UNUSED(oldval); VLC_UNUSED(newval);
608 intf_thread_t *p_intf = (intf_thread_t *) p_data;
609 FollowAnchor( p_intf );
614 void GoBack( intf_thread_t *p_intf )
617 history_t *p_history = NULL;
618 history_item_t *p_history_item = NULL;
619 history_item_t *p_new_history_item = NULL;
620 playlist_t *p_playlist = NULL;
621 char *psz_timed_url = NULL;
622 playlist_item_t *p_current_item;
624 #ifdef CMML_INTF_DEBUG
625 msg_Dbg( p_intf, "Going back in navigation history" );
628 /* Find the playlist */
629 p_playlist = pl_Hold( p_intf );
631 /* Retrieve navigation history from playlist */
632 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
635 /* History doesn't exist yet: ignore user's request */
636 msg_Warn( p_intf, "can't go back: no history exists yet" );
637 pl_Release( p_intf );
641 p_history = history.p_address;
642 #ifdef CMML_INTF_DEBUG
643 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
644 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
645 p_history->p_xarray );
648 /* Check whether we can go back in the history */
649 if( history_CanGoBack( p_history ) == false )
651 msg_Warn( p_intf, "can't go back: already at beginning of history" );
652 pl_Release( p_intf );
656 p_current_item = playlist_CurrentPlayingItem( p_playlist );
658 /* Save the currently-playing media in a new history item */
659 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
660 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
661 free( psz_timed_url );
663 if( !p_new_history_item )
665 #ifdef CMML_INTF_DEBUG
666 msg_Dbg( p_intf, "back: could not initialise new history item" );
668 pl_Release( p_intf );
672 /* Go back in the history, saving the currently-playing item */
673 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
674 p_history_item = history_Item( p_history );
676 #ifdef CMML_INTF_DEBUG
677 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
678 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
679 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
682 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
683 pl_Release( p_intf );
687 void GoForward( intf_thread_t *p_intf )
690 history_t *p_history = NULL;
691 history_item_t *p_history_item = NULL;
692 history_item_t *p_new_history_item = NULL;
693 playlist_t *p_playlist = NULL;
694 playlist_item_t *p_current_item;
696 #ifdef CMML_INTF_DEBUG
697 msg_Dbg( p_intf, "Going forward in navigation history" );
700 /* Find the playlist */
701 p_playlist = pl_Hold( p_intf );
703 /* Retrieve navigation history from playlist */
704 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
707 /* History doesn't exist yet: ignore user's request */
708 msg_Warn( p_intf, "can't go back: no history exists yet" );
709 pl_Release( p_intf );
713 p_history = history.p_address;
714 #ifdef CMML_INTF_DEBUG
715 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
716 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
717 p_history->p_xarray );
720 /* Check whether we can go forward in the history */
721 if( history_CanGoForward( p_history ) == false )
723 msg_Warn( p_intf, "can't go forward: already at end of history" );
724 pl_Release( p_intf );
728 /* Save the currently-playing media in a new history item */
729 p_new_history_item = malloc( sizeof(history_item_t) );
730 if( !p_new_history_item )
732 #ifdef CMML_INTF_DEBUG
733 msg_Dbg( p_intf, "forward: could not initialise new history item" );
735 pl_Release( p_intf );
738 p_current_item = playlist_CurrentPlayingItem( p_playlist );
739 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
741 p_new_history_item->psz_name = p_new_history_item->psz_uri;
743 /* Go forward in the history, saving the currently-playing item */
744 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
745 p_history_item = history_Item( p_history );
747 #ifdef CMML_INTF_DEBUG
748 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
749 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
750 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
753 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
754 pl_Release( p_intf );
757 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
759 playlist_Stop( p_playlist );
760 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
761 PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, true /* FIXME: p_playlist->status.i_index */,
765 /****************************************************************************
766 * DisplayAnchor: displays an anchor on the given video output
767 ****************************************************************************/
768 static int DisplayAnchor( intf_thread_t *p_intf,
769 vout_thread_t *p_vout,
770 char *psz_anchor_description,
771 char *psz_anchor_url )
773 int i_margin_h, i_margin_v;
785 /* Should display subtitle underlined and in blue, but it looks
786 * like VLC doesn't implement any text styles yet. D'oh! */
787 // p_style = &blue_with_underline;
791 /* TODO: p_subpicture doesn't have the proper i_x and i_y
792 * coordinates. Need to look at the subpicture display system to
794 if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
795 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
796 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
798 /* Displayed successfully */
807 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
814 static history_t * GetHistory( playlist_t *p_playlist )
817 history_t *p_history = NULL;
819 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
821 /* history doesn't exist yet: need to create it */
822 history_t *new_history = history_New();
823 val.p_address = new_history;
824 var_Create( p_playlist, "navigation-history",
825 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
826 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
828 msg_Warn( p_playlist, "could not initialise history" );
832 p_history = new_history;
833 #ifdef CMML_INTF_HISTORY_DEBUG
834 msg_Dbg( p_playlist, "nav history created at %p", new_history );
835 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
836 p_history->i_index, p_history->p_xarray );
842 p_history = val.p_address;
843 #ifdef CMML_INTF_HISTORY_DEBUG
844 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );