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 VideoLAN
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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
30 #include <stdlib.h> /* malloc(), free() */
35 #include <vlc/decoder.h>
41 #include "stream_control.h"
42 #include "input_ext-intf.h"
43 #include "input_ext-dec.h"
47 #include "browser_open.h"
52 #undef CMML_INTF_USE_TIMED_URIS
54 #undef CMML_INTF_DEBUG
55 #undef CMML_INTF_SUBPICTURE_DEBUG
56 #undef CMML_INTF_HISTORY_DEBUG
58 /*****************************************************************************
59 * intf_sys_t: description and status of interface
60 *****************************************************************************/
63 decoder_t * p_cmml_decoder;
64 input_thread_t * p_input;
66 vlc_bool_t b_key_pressed;
69 struct navigation_history_t
75 /*****************************************************************************
77 *****************************************************************************/
78 static int InitThread ( intf_thread_t * );
79 static int MouseEvent ( vlc_object_t *, char const *,
80 vlc_value_t, vlc_value_t, void * );
81 static int KeyEvent ( vlc_object_t *, char const *,
82 vlc_value_t, vlc_value_t, void * );
84 static void FollowAnchor ( intf_thread_t * );
85 static void GoBack ( intf_thread_t * );
86 static void GoForward ( intf_thread_t * );
88 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
89 static char *GetTimedURIFragmentForTime ( int );
90 static int GetCurrentTimeInSeconds ( input_thread_t * );
91 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
93 static history_t * GetHistory ( playlist_t * );
94 static void ReplacePlaylistItem ( playlist_t *, char * );
96 /* Exported functions */
97 static void RunIntf ( intf_thread_t *p_intf );
99 /*****************************************************************************
100 * OpenIntf: initialize CMML interface
101 *****************************************************************************/
102 int E_(OpenIntf) ( vlc_object_t *p_this )
104 intf_thread_t *p_intf = (intf_thread_t *)p_this;
106 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
107 if( p_intf->p_sys == NULL )
112 p_intf->pf_run = RunIntf;
114 var_AddCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
115 /* we also need to add the callback for "mouse-clicked", but do that later
116 * when we've found a p_vout */
121 /*****************************************************************************
122 * CloseIntf: destroy dummy interface
123 *****************************************************************************/
124 void E_(CloseIntf) ( vlc_object_t *p_this )
126 intf_thread_t * p_intf = (intf_thread_t *)p_this;
127 vout_thread_t * p_vout;
129 #ifdef CMML_INTF_DEBUG
130 msg_Dbg( p_intf, "freeing CMML interface" );
133 /* Erase the anchor text description from the video output if it exists */
134 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
135 if( p_vout != NULL && p_vout->p_subpicture != NULL )
137 subpicture_t *p_subpic;
140 for( i_subpic = 0; i_subpic < VOUT_MAX_SUBPICTURES; i_subpic++ )
142 p_subpic = &p_vout->p_subpicture[i_subpic];
144 if( p_subpic != NULL &&
145 ( p_subpic->i_status == RESERVED_SUBPICTURE
146 || p_subpic->i_status == READY_SUBPICTURE ) )
148 vout_DestroySubPicture( p_vout, p_subpic );
152 if( p_vout ) vlc_object_release( p_vout );
154 var_DelCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
156 vlc_object_release( p_intf->p_sys->p_cmml_decoder );
158 free( p_intf->p_sys );
162 /*****************************************************************************
164 *****************************************************************************/
165 static void RunIntf( intf_thread_t *p_intf )
167 vout_thread_t * p_vout = NULL;
169 if( InitThread( p_intf ) < 0 )
171 msg_Err( p_intf, "can't initialize CMML interface" );
174 #ifdef CMML_INTF_DEBUG
175 msg_Dbg( p_intf, "CMML intf initialized" );
178 /* if video output is dying, disassociate ourselves from it */
179 if( p_vout && p_vout->b_die )
181 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
182 vlc_object_release( p_vout );
187 while( !p_intf->b_die )
190 decoder_t *p_cmml_decoder;
192 /* find a video output if we currently don't have one */
195 p_vout = vlc_object_find( p_intf->p_sys->p_input,
196 VLC_OBJECT_VOUT, FIND_CHILD );
199 #ifdef CMML_INTF_DEBUG
200 msg_Dbg( p_intf, "found vout thread" );
202 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
206 vlc_mutex_lock( &p_intf->change_lock );
211 if( p_intf->p_sys->b_key_pressed )
214 int i, i_action = -1;
215 struct hotkey *p_hotkeys = p_intf->p_vlc->p_hotkeys;
217 /* Find action triggered by hotkey (if any) */
218 var_Get( p_intf->p_vlc, "key-pressed", &val );
220 /* Acknowledge that we've handled the b_key_pressed event */
221 p_intf->p_sys->b_key_pressed = VLC_FALSE;
223 #ifdef CMML_INTF_DEBUG
224 msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
227 for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
229 if( p_hotkeys[i].i_key == val.i_int )
230 i_action = p_hotkeys[i].i_action;
233 /* What did the user do? */
238 case ACTIONID_NAV_ACTIVATE:
239 FollowAnchor( p_intf );
241 case ACTIONID_HISTORY_BACK:
244 case ACTIONID_HISTORY_FORWARD:
253 vlc_mutex_unlock( &p_intf->change_lock );
256 * Get a pending anchor description/URL from the CMML decoder
257 * and display it on screen
259 p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
260 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
265 char *psz_description = NULL;
266 char *psz_url = NULL;
268 psz_description = val.p_address;
270 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
273 psz_url = val.p_address;
278 if( DisplayAnchor( p_intf, p_vout, psz_description,
279 psz_url ) != VLC_SUCCESS )
281 /* text render unsuccessful: do nothing */
285 /* text render successful: clear description */
286 val.p_address = NULL;
287 if( var_Set( p_cmml_decoder,
288 "psz-current-anchor-description", val ) !=
291 msg_Dbg( p_intf, "reset of "
292 "psz-current-anchor-description failed" );
294 free( psz_description );
302 msleep( INTF_IDLE_SLEEP );
305 /* if we're here, the video output is dying: release the vout object */
309 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
310 vlc_object_release( p_vout );
313 vlc_object_release( p_intf->p_sys->p_input );
316 /*****************************************************************************
318 *****************************************************************************/
319 static int InitThread( intf_thread_t * p_intf )
321 /* We might need some locking here */
324 input_thread_t * p_input;
325 decoder_t *p_cmml_decoder;
327 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
328 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
330 #ifdef CMML_INTF_DEBUG
331 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
332 p_cmml_decoder, p_input );
335 /* Maybe the input just died */
336 if( p_input == NULL )
341 vlc_mutex_lock( &p_intf->change_lock );
343 p_intf->p_sys->p_input = p_input;
344 p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
346 p_intf->p_sys->b_key_pressed = VLC_FALSE;
348 vlc_mutex_unlock( &p_intf->change_lock );
358 /*****************************************************************************
359 * MouseEvent: callback for mouse events
360 *****************************************************************************/
361 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
362 vlc_value_t oldval, vlc_value_t newval, void *p_data )
364 /* TODO: handle mouse clicks on the anchor text */
369 /*****************************************************************************
370 * KeyEvent: callback for keyboard events
371 *****************************************************************************/
372 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
373 vlc_value_t oldval, vlc_value_t newval, void *p_data )
375 intf_thread_t *p_intf = (intf_thread_t *)p_data;
376 vlc_mutex_lock( &p_intf->change_lock );
378 p_intf->p_sys->b_key_pressed = VLC_TRUE;
380 vlc_mutex_unlock( &p_intf->change_lock );
385 /*****************************************************************************
386 * FollowAnchor: follow the current anchor being displayed to the user
387 *****************************************************************************/
388 static void FollowAnchor ( intf_thread_t *p_intf )
391 decoder_t *p_cmml_decoder;
392 char *psz_url = NULL;
395 msg_Dbg( p_intf, "User followed anchor" );
397 p_sys = p_intf->p_sys;
398 p_cmml_decoder = p_sys->p_cmml_decoder;
400 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
403 if( val.p_address ) psz_url = val.p_address;
406 #ifdef CMML_INTF_DEBUG
407 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
412 playlist_t *p_playlist;
413 playlist_item_t *p_current_item;
414 char *psz_uri_to_load;
416 p_playlist = (playlist_t *) vlc_object_find( p_intf,
417 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
420 msg_Warn( p_intf, "can't find playlist" );
425 p_current_item = p_playlist->pp_items[p_playlist->i_index];
426 #ifdef CMML_INTF_DEBUG
427 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"",
428 p_current_item->psz_uri );
431 psz_uri_to_load = XURL_Concat( p_current_item->input.psz_uri,
434 #ifdef CMML_INTF_DEBUG
435 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 (void) browser_Open( psz_url );
496 playlist_Command( p_playlist, PLAYLIST_PAUSE, 0 );
499 free( psz_uri_to_load );
501 vlc_object_release( p_playlist );
506 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
507 playlist_item_t *p_current_item )
509 #ifdef CMML_INTF_USE_TIMED_URIS
510 char *psz_url = NULL;
511 char *psz_return_value = NULL;
512 char *psz_seconds = NULL;
515 psz_url = XURL_GetWithoutFragment( p_current_item->input->psz_uri );
517 /* Get current time as a string */
518 if( XURL_IsFileURL( psz_url ) == VLC_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 /* Suppress warning messages about unused functions */
538 p = GetTimedURIFragmentForTime; /* unused */
539 p = GetCurrentTimeInSeconds; /* unused */
541 return strdup( p_current_item->input.psz_uri );
547 * Get the current time, rounded down to the nearest second
549 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
552 int GetCurrentTimeInSeconds( input_thread_t *p_input )
557 var_Get( p_input, "time", &time );
558 i_seconds = time.i_time / 1000000;
564 char *GetTimedURIFragmentForTime( int seconds )
568 asprintf( &psz_time, "%d", seconds );
574 void GoBack( intf_thread_t *p_intf )
577 history_t *p_history = NULL;
578 history_item_t *p_history_item = NULL;
579 history_item_t *p_new_history_item = NULL;
580 playlist_t *p_playlist = NULL;
581 char *psz_timed_url = NULL;
583 #ifdef CMML_INTF_DEBUG
584 msg_Dbg( p_intf, "Going back in navigation history" );
587 /* Find the playlist */
588 p_playlist = (playlist_t *) vlc_object_find( p_intf,
589 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
592 msg_Warn( p_intf, "can't find playlist" );
596 /* Retrieve navigation history from playlist */
597 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
600 /* History doesn't exist yet: ignore user's request */
601 msg_Warn( p_intf, "can't go back: no history exists yet" );
602 vlc_object_release( p_playlist );
606 p_history = history.p_address;
607 #ifdef CMML_INTF_DEBUG
608 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
609 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
610 p_history->p_xarray );
613 /* Check whether we can go back in the history */
614 if( history_CanGoBack( p_history ) == VLC_FALSE )
616 msg_Warn( p_intf, "can't go back: already at beginning of history" );
617 vlc_object_release( p_playlist );
621 playlist_item_t *p_current_item;
622 p_current_item = p_playlist->pp_items[p_playlist->i_index];
624 /* Save the currently-playing media in a new history item */
625 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
626 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
627 free( psz_timed_url );
629 if( !p_new_history_item )
631 #ifdef CMML_INTF_DEBUG
632 msg_Dbg( p_intf, "back: could not initialise new history item" );
634 vlc_object_release( p_playlist );
638 /* Go back in the history, saving the currently-playing item */
639 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
640 p_history_item = history_Item( p_history );
642 #ifdef CMML_INTF_DEBUG
643 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
644 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
645 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
648 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
649 vlc_object_release( p_playlist );
653 void GoForward( intf_thread_t *p_intf )
656 history_t *p_history = NULL;
657 history_item_t *p_history_item = NULL;
658 history_item_t *p_new_history_item = NULL;
659 playlist_t *p_playlist = NULL;
661 #ifdef CMML_INTF_DEBUG
662 msg_Dbg( p_intf, "Going forward in navigation history" );
665 /* Find the playlist */
666 p_playlist = (playlist_t *) vlc_object_find( p_intf,
667 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
670 msg_Warn( p_intf, "can't find playlist" );
674 /* Retrieve navigation history from playlist */
675 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
678 /* History doesn't exist yet: ignore user's request */
679 msg_Warn( p_intf, "can't go back: no history exists yet" );
680 vlc_object_release( p_playlist );
684 p_history = history.p_address;
685 #ifdef CMML_INTF_DEBUG
686 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
687 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
688 p_history->p_xarray );
691 /* Check whether we can go forward in the history */
692 if( history_CanGoForward( p_history ) == VLC_FALSE )
694 msg_Warn( p_intf, "can't go forward: already at end of history" );
695 vlc_object_release( p_playlist );
699 /* Save the currently-playing media in a new history item */
700 p_new_history_item = malloc( sizeof(history_item_t) );
701 if( !p_new_history_item )
703 #ifdef CMML_INTF_DEBUG
704 msg_Dbg( p_intf, "forward: could not initialise new history item" );
706 vlc_object_release( p_playlist );
709 playlist_item_t *p_current_item;
710 p_current_item = p_playlist->pp_items[p_playlist->i_index];
711 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
713 p_new_history_item->psz_name = p_new_history_item->psz_uri;
715 /* Go forward in the history, saving the currently-playing item */
716 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
717 p_history_item = history_Item( p_history );
719 #ifdef CMML_INTF_DEBUG
720 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
721 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
722 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
725 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
726 vlc_object_release( p_playlist );
729 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
731 playlist_Stop( p_playlist );
732 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
733 PLAYLIST_REPLACE, p_playlist->i_index );
734 playlist_Goto( p_playlist, p_playlist->i_index );
737 /****************************************************************************
738 * DisplayAnchor: displays an anchor on the given video output
739 ****************************************************************************/
740 static int DisplayAnchor( intf_thread_t *p_intf,
741 vout_thread_t *p_vout,
742 char *psz_anchor_description,
743 char *psz_anchor_url )
745 int i_margin_h, i_margin_v;
755 text_style_t *p_style = NULL;
757 text_style_t blue_with_underline = default_text_style;
758 blue_with_underline.b_underline = VLC_TRUE;
759 blue_with_underline.i_color = 0x22ff22;
763 /* Should display subtitle underlined and in blue,
764 * but it looks like VLC doesn't implement any
765 * text styles yet. D'oh! */
766 p_style = &blue_with_underline;
770 /* TODO: p_subpicture doesn't have the proper i_x and i_y
771 * coordinates. Need to look at the subpicture display system to
773 if ( vout_ShowTextAbsolute( p_vout,
774 psz_anchor_description, p_style, OSD_ALIGN_BOTTOM,
775 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
777 /* Displayed successfully */
778 #ifdef CMML_INTF_SUBPICTURE_DEBUG
779 msg_Dbg( p_intf, "subpicture created at (%d, %d) (%d, %d)",
780 p_subpicture->i_x, p_subpicture->i_y,
781 p_subpicture->i_width, p_subpicture->i_height );
792 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
799 static history_t * GetHistory( playlist_t *p_playlist )
802 history_t *p_history = NULL;
804 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
806 /* history doesn't exist yet: need to create it */
807 history_t *new_history = history_New();
808 val.p_address = new_history;
809 var_Create( p_playlist, "navigation-history",
810 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
811 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
813 msg_Warn( p_playlist, "could not initialise history" );
817 p_history = new_history;
818 #ifdef CMML_INTF_HISTORY_DEBUG
819 msg_Dbg( p_playlist, "nav history created at %p", new_history );
820 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
821 p_history->i_index, p_history->p_xarray );
827 p_history = val.p_address;
828 #ifdef CMML_INTF_HISTORY_DEBUG
829 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );