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 *****************************************************************************/
64 decoder_t * p_cmml_decoder;
65 input_thread_t * p_input;
70 struct navigation_history_t
76 /*****************************************************************************
78 *****************************************************************************/
80 int OpenIntf ( vlc_object_t * );
81 void CloseIntf ( vlc_object_t * );
83 static int InitThread ( intf_thread_t * );
84 static int MouseEvent ( vlc_object_t *, char const *,
85 vlc_value_t, vlc_value_t, void * );
86 static int KeyEvent ( vlc_object_t *, char const *,
87 vlc_value_t, vlc_value_t, void * );
89 static void FollowAnchor ( intf_thread_t * );
90 static void GoBack ( intf_thread_t * );
91 static void GoForward ( intf_thread_t * );
93 static int FollowAnchorCallback ( vlc_object_t *, char const *,
94 vlc_value_t, vlc_value_t, void * );
95 static int GoBackCallback ( vlc_object_t *, char const *,
96 vlc_value_t, vlc_value_t, void * );
97 static int GoForwardCallback ( vlc_object_t *, char const *,
98 vlc_value_t, vlc_value_t, void * );
100 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
101 #ifdef CMML_INTF_USE_TIMED_URIS
102 static int GetCurrentTimeInSeconds ( input_thread_t * );
103 static char *GetTimedURIFragmentForTime ( int );
105 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
107 static int DisplayPendingAnchor ( intf_thread_t *, vout_thread_t * );
108 static history_t * GetHistory ( playlist_t * );
109 static void ReplacePlaylistItem ( playlist_t *, char * );
111 /* Exported functions */
112 static void RunIntf ( intf_thread_t *p_intf );
114 /*****************************************************************************
115 * OpenIntf: initialize CMML interface
116 *****************************************************************************/
117 int OpenIntf ( vlc_object_t *p_this )
119 intf_thread_t *p_intf = (intf_thread_t *)p_this;
121 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
122 if( p_intf->p_sys == NULL )
125 p_intf->pf_run = RunIntf;
127 var_AddCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
128 /* we also need to add the callback for "mouse-clicked", but do that later
129 * when we've found a p_vout */
131 var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
132 var_AddCallback( p_intf->p_libvlc, "browse-go-back",
133 GoBackCallback, p_intf );
134 var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
135 var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
136 GoForwardCallback, p_intf );
137 var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
138 var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
139 FollowAnchorCallback, p_intf );
144 /*****************************************************************************
145 * CloseIntf: destroy dummy interface
146 *****************************************************************************/
147 void CloseIntf ( vlc_object_t *p_this )
149 intf_thread_t * p_intf = (intf_thread_t *)p_this;
150 vout_thread_t * p_vout;
152 #ifdef CMML_INTF_DEBUG
153 msg_Dbg( p_intf, "freeing CMML interface" );
156 /* erase the anchor text description from the video output if it exists */
157 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
160 /* enable CMML as a subtitle track */
161 spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
162 vlc_object_release( p_vout );
165 var_DelCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
167 vlc_object_release( p_intf->p_sys->p_cmml_decoder );
169 free( p_intf->p_sys );
173 /*****************************************************************************
175 *****************************************************************************/
176 static void RunIntf( intf_thread_t *p_intf )
178 int canc = vlc_savecancel();
179 vout_thread_t * p_vout = NULL;
181 if( InitThread( p_intf ) < 0 )
183 msg_Err( p_intf, "can't initialize CMML interface" );
186 #ifdef CMML_INTF_DEBUG
187 msg_Dbg( p_intf, "CMML intf initialized" );
191 while( vlc_object_alive (p_intf) )
193 /* if video output is dying, disassociate ourselves from it */
194 if( p_vout && !vlc_object_alive (p_vout) )
196 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
197 vlc_object_release( p_vout );
201 /* find a video output if we currently don't have one */
204 p_vout = vlc_object_find( p_intf->p_sys->p_input,
205 VLC_OBJECT_VOUT, FIND_CHILD );
208 #ifdef CMML_INTF_DEBUG
209 msg_Dbg( p_intf, "found vout thread" );
211 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
215 vlc_mutex_lock( &p_intf->change_lock );
220 switch( p_intf->p_sys->i_key_action )
222 case ACTIONID_NAV_ACTIVATE:
223 FollowAnchor( p_intf );
225 case ACTIONID_HISTORY_BACK:
228 case ACTIONID_HISTORY_FORWARD:
234 p_intf->p_sys->i_key_action = 0;
235 vlc_mutex_unlock( &p_intf->change_lock );
237 (void) DisplayPendingAnchor( p_intf, p_vout );
240 msleep( INTF_IDLE_SLEEP );
243 /* if we're here, the video output is dying: release the vout object */
247 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
248 vlc_object_release( p_vout );
251 vlc_object_release( p_intf->p_sys->p_input );
252 vlc_restorecancel( canc );
255 /*****************************************************************************
256 * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
257 * decoder and display it on screen
258 *****************************************************************************/
259 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
261 decoder_t *p_cmml_decoder;
262 char *psz_description = NULL;
263 char *psz_url = NULL;
265 intf_thread_t *p_primary_intf;
268 p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
269 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
278 psz_description = val.p_address;
280 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
283 psz_url = val.p_address;
288 /* display anchor as subtitle on-screen */
289 if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
292 /* text render unsuccessful: do nothing */
296 /* text render successful: clear description */
297 val.p_address = NULL;
298 if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
302 "reset of psz-current-anchor-description failed" );
304 free( psz_description );
312 /*****************************************************************************
314 *****************************************************************************/
315 static int InitThread( intf_thread_t * p_intf )
317 /* We might need some locking here */
318 if( vlc_object_alive (p_intf) )
320 input_thread_t * p_input;
321 decoder_t *p_cmml_decoder;
323 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
324 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
326 #ifdef CMML_INTF_DEBUG
327 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
328 p_cmml_decoder, p_input );
331 /* Maybe the input just died */
332 if( p_input == NULL )
337 vlc_mutex_lock( &p_intf->change_lock );
339 p_intf->p_sys->p_input = p_input;
340 p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
342 p_intf->p_sys->i_key_action = 0;
344 vlc_mutex_unlock( &p_intf->change_lock );
354 /*****************************************************************************
355 * MouseEvent: callback for mouse events
356 *****************************************************************************/
357 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
358 vlc_value_t oldval, vlc_value_t newval, void *p_data )
360 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
361 VLC_UNUSED(oldval); VLC_UNUSED(newval);
363 /* TODO: handle mouse clicks on the anchor text */
368 /*****************************************************************************
369 * KeyEvent: callback for keyboard events
370 *****************************************************************************/
371 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
372 vlc_value_t oldval, vlc_value_t newval, void *p_data )
374 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
375 VLC_UNUSED(oldval); VLC_UNUSED(newval);
376 intf_thread_t *p_intf = (intf_thread_t *)p_data;
377 vlc_mutex_lock( &p_intf->change_lock );
379 p_intf->p_sys->i_key_action = newval.i_int;
381 vlc_mutex_unlock( &p_intf->change_lock );
386 /*****************************************************************************
387 * FollowAnchor: follow the current anchor being displayed to the user
388 *****************************************************************************/
389 static void FollowAnchor ( intf_thread_t *p_intf )
392 decoder_t *p_cmml_decoder;
393 char *psz_url = NULL;
396 msg_Dbg( p_intf, "User followed anchor" );
398 p_sys = p_intf->p_sys;
399 p_cmml_decoder = p_sys->p_cmml_decoder;
401 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
404 if( val.p_address ) psz_url = val.p_address;
407 #ifdef CMML_INTF_DEBUG
408 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
413 playlist_t *p_playlist;
414 playlist_item_t *p_current_item;
415 char *psz_uri_to_load;
419 p_playlist = pl_Hold( p_intf );
422 p_current_item = playlist_CurrentPlayingItem( p_playlist );
423 char *psz_uri = input_item_GetURI( p_current_item->p_input );
424 #ifdef CMML_INTF_DEBUG
425 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
428 psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
431 #ifdef CMML_INTF_DEBUG
432 msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
435 if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
437 msg_Dbg( p_intf, "couldn't get time from current clip" );
440 i_seconds = time.i_time / 1000000;
441 #ifdef CMML_INTF_DEBUG
442 msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
445 /* TODO: we need a (much) more robust way of detecting whether
446 * the file's a media file ... */
447 if( strstr( psz_uri_to_load, ".anx" ) != NULL )
449 history_t *p_history = NULL;
450 history_item_t *p_history_item = NULL;
453 p_history = GetHistory( p_playlist );
455 /* create history item */
456 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
457 p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
458 free( psz_timed_url );
460 if( !p_history_item )
462 msg_Warn( p_intf, "could not initialise history item" );
466 #ifdef CMML_INTF_DEBUG
467 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
469 history_PruneAndInsert( p_history, p_history_item );
470 #ifdef CMML_INTF_DEBUG
471 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
472 p_history_item, p_history_item->psz_uri );
473 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
477 /* free current-anchor-url */
479 val.p_address = NULL;
480 if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
483 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
486 ReplacePlaylistItem( p_playlist, psz_uri_to_load );
490 #ifdef CMML_INTF_DEBUG
491 msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
493 (void) browser_Open( psz_url );
494 playlist_Control( p_playlist, PLAYLIST_PAUSE, pl_Unlocked, 0 );
497 free( psz_uri_to_load );
499 vlc_object_release( p_playlist );
504 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
505 playlist_item_t *p_current_item )
507 #ifdef CMML_INTF_USE_TIMED_URIS
508 char *psz_url = NULL;
509 char *psz_return_value = NULL;
510 char *psz_seconds = NULL;
513 char *psz_uri = input_item_GetURI( p_current_item->p_input );
514 psz_url = XURL_GetWithoutFragment( psz_uri );
517 /* Get current time as a string */
518 if( XURL_IsFileURL( psz_url ) == true )
519 psz_url = xstrcat( psz_url, "#" );
521 psz_url = xstrcat( psz_url, "?" );
523 /* jump back to 2 seconds before where we are now */
524 i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
525 psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
528 psz_url = xstrcat( psz_url, psz_seconds );
530 psz_return_value = psz_url;
533 return psz_return_value;
537 return input_item_GetURI( p_current_item->p_input );
543 #ifdef CMML_INTF_USE_TIMED_URIS
545 * Get the current time, rounded down to the nearest second
547 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
550 int GetCurrentTimeInSeconds( input_thread_t *p_input )
555 var_Get( p_input, "time", &time );
556 i_seconds = time.i_time / 1000000;
561 char *GetTimedURIFragmentForTime( int seconds )
565 if( asprintf( &psz_time, "%d", seconds ) == -1 )
572 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
573 vlc_value_t oldval, vlc_value_t newval, void *p_data )
575 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
576 VLC_UNUSED(oldval); VLC_UNUSED(newval);
577 intf_thread_t *p_intf = (intf_thread_t *) p_data;
583 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
584 vlc_value_t oldval, vlc_value_t newval, void *p_data )
586 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
587 VLC_UNUSED(oldval); VLC_UNUSED(newval);
588 intf_thread_t *p_intf = (intf_thread_t *) p_data;
594 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
595 vlc_value_t oldval, vlc_value_t newval,
598 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
599 VLC_UNUSED(oldval); VLC_UNUSED(newval);
600 intf_thread_t *p_intf = (intf_thread_t *) p_data;
601 FollowAnchor( p_intf );
606 void GoBack( intf_thread_t *p_intf )
609 history_t *p_history = NULL;
610 history_item_t *p_history_item = NULL;
611 history_item_t *p_new_history_item = NULL;
612 playlist_t *p_playlist = NULL;
613 char *psz_timed_url = NULL;
614 playlist_item_t *p_current_item;
616 #ifdef CMML_INTF_DEBUG
617 msg_Dbg( p_intf, "Going back in navigation history" );
620 /* Find the playlist */
621 p_playlist = pl_Hold( p_intf );
623 /* Retrieve navigation history from playlist */
624 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
627 /* History doesn't exist yet: ignore user's request */
628 msg_Warn( p_intf, "can't go back: no history exists yet" );
629 vlc_object_release( p_playlist );
633 p_history = history.p_address;
634 #ifdef CMML_INTF_DEBUG
635 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
636 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
637 p_history->p_xarray );
640 /* Check whether we can go back in the history */
641 if( history_CanGoBack( p_history ) == false )
643 msg_Warn( p_intf, "can't go back: already at beginning of history" );
644 vlc_object_release( p_playlist );
648 p_current_item = playlist_CurrentPlayingItem( p_playlist );
650 /* Save the currently-playing media in a new history item */
651 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
652 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
653 free( psz_timed_url );
655 if( !p_new_history_item )
657 #ifdef CMML_INTF_DEBUG
658 msg_Dbg( p_intf, "back: could not initialise new history item" );
660 vlc_object_release( p_playlist );
664 /* Go back in the history, saving the currently-playing item */
665 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
666 p_history_item = history_Item( p_history );
668 #ifdef CMML_INTF_DEBUG
669 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
670 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
671 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
674 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
675 vlc_object_release( p_playlist );
679 void GoForward( intf_thread_t *p_intf )
682 history_t *p_history = NULL;
683 history_item_t *p_history_item = NULL;
684 history_item_t *p_new_history_item = NULL;
685 playlist_t *p_playlist = NULL;
686 playlist_item_t *p_current_item;
688 #ifdef CMML_INTF_DEBUG
689 msg_Dbg( p_intf, "Going forward in navigation history" );
692 /* Find the playlist */
693 p_playlist = pl_Hold( p_intf );
695 /* Retrieve navigation history from playlist */
696 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
699 /* History doesn't exist yet: ignore user's request */
700 msg_Warn( p_intf, "can't go back: no history exists yet" );
701 vlc_object_release( p_playlist );
705 p_history = history.p_address;
706 #ifdef CMML_INTF_DEBUG
707 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
708 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
709 p_history->p_xarray );
712 /* Check whether we can go forward in the history */
713 if( history_CanGoForward( p_history ) == false )
715 msg_Warn( p_intf, "can't go forward: already at end of history" );
716 vlc_object_release( p_playlist );
720 /* Save the currently-playing media in a new history item */
721 p_new_history_item = malloc( sizeof(history_item_t) );
722 if( !p_new_history_item )
724 #ifdef CMML_INTF_DEBUG
725 msg_Dbg( p_intf, "forward: could not initialise new history item" );
727 vlc_object_release( p_playlist );
730 p_current_item = playlist_CurrentPlayingItem( p_playlist );
731 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
733 p_new_history_item->psz_name = p_new_history_item->psz_uri;
735 /* Go forward in the history, saving the currently-playing item */
736 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
737 p_history_item = history_Item( p_history );
739 #ifdef CMML_INTF_DEBUG
740 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
741 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
742 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
745 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
746 vlc_object_release( p_playlist );
749 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
751 playlist_Stop( p_playlist );
752 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
753 PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, true /* FIXME: p_playlist->status.i_index */,
757 /****************************************************************************
758 * DisplayAnchor: displays an anchor on the given video output
759 ****************************************************************************/
760 static int DisplayAnchor( intf_thread_t *p_intf,
761 vout_thread_t *p_vout,
762 char *psz_anchor_description,
763 char *psz_anchor_url )
765 int i_margin_h, i_margin_v;
777 /* Should display subtitle underlined and in blue, but it looks
778 * like VLC doesn't implement any text styles yet. D'oh! */
779 // p_style = &blue_with_underline;
783 /* TODO: p_subpicture doesn't have the proper i_x and i_y
784 * coordinates. Need to look at the subpicture display system to
786 if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
787 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
788 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
790 /* Displayed successfully */
799 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
806 static history_t * GetHistory( playlist_t *p_playlist )
809 history_t *p_history = NULL;
811 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
813 /* history doesn't exist yet: need to create it */
814 history_t *new_history = history_New();
815 val.p_address = new_history;
816 var_Create( p_playlist, "navigation-history",
817 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
818 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
820 msg_Warn( p_playlist, "could not initialise history" );
824 p_history = new_history;
825 #ifdef CMML_INTF_HISTORY_DEBUG
826 msg_Dbg( p_playlist, "nav history created at %p", new_history );
827 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
828 p_history->i_index, p_history->p_xarray );
834 p_history = val.p_address;
835 #ifdef CMML_INTF_HISTORY_DEBUG
836 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );