]> git.sesse.net Git - vlc/blob - modules/codec/cmml/intf.c
* Added Continuous Media Markup Language (CMML) codec
[vlc] / modules / codec / cmml / intf.c
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
7  *
8  * $Id$
9  *
10  * Authors: Andre Pang <Andre.Pang@csiro.au>
11  *
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.
16  * 
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.
21  *
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  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #include <stdlib.h>                                      /* malloc(), free() */
31 #include <string.h>
32 #include <unistd.h>
33
34 #include <vlc/vlc.h>
35 #include <vlc/decoder.h>
36 #include <vlc/intf.h>
37 #include <vlc/vout.h>
38
39 #include <osd.h>
40
41 #include "stream_control.h"
42 #include "input_ext-intf.h"
43 #include "input_ext-dec.h"
44
45 #include "vlc_keys.h"
46
47 #include "browser_open.h"
48 #include "history.h"
49 #include "xstrcat.h"
50 #include "xurl.h"
51
52 #undef  CMML_INTF_USE_TIMED_URIS
53
54 #undef  CMML_INTF_DEBUG
55 #undef  CMML_INTF_SUBPICTURE_DEBUG
56 #undef  CMML_INTF_HISTORY_DEBUG
57
58 /*****************************************************************************
59  * intf_sys_t: description and status of interface
60  *****************************************************************************/
61 struct intf_sys_t
62 {
63     decoder_t *         p_cmml_decoder;
64     input_thread_t *    p_input;
65
66     vlc_bool_t          b_key_pressed;
67 };
68
69 struct navigation_history_t
70 {
71     int i_history_size;
72     int i_last_item;
73 };
74
75 /*****************************************************************************
76  * Local prototypes.
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 * );
83
84 static void  FollowAnchor               ( intf_thread_t * );
85 static void  GoBack                     ( intf_thread_t * );
86 static void  GoForward                  ( intf_thread_t * );
87
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 *,
92                                           char *, char * );
93 static history_t * GetHistory           ( playlist_t * );
94 static void  ReplacePlaylistItem        ( playlist_t *, char * );
95
96 /* Exported functions */
97 static void RunIntf        ( intf_thread_t *p_intf );
98
99 /*****************************************************************************
100  * OpenIntf: initialize CMML interface
101  *****************************************************************************/
102 int E_(OpenIntf) ( vlc_object_t *p_this )
103 {
104     intf_thread_t *p_intf = (intf_thread_t *)p_this;
105
106     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
107     if( p_intf->p_sys == NULL )
108     {
109         return( 1 );
110     };
111
112     p_intf->pf_run = RunIntf;
113     
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 */
117
118     return( 0 );
119 }
120
121 /*****************************************************************************
122  * CloseIntf: destroy dummy interface
123  *****************************************************************************/
124 void E_(CloseIntf) ( vlc_object_t *p_this )
125 {
126     intf_thread_t * p_intf = (intf_thread_t *)p_this;
127     vout_thread_t * p_vout;
128
129 #ifdef CMML_INTF_DEBUG
130     msg_Dbg( p_intf, "freeing CMML interface" );
131 #endif
132
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 )
136     {
137         subpicture_t *p_subpic;
138         int          i_subpic;
139
140         for( i_subpic = 0; i_subpic < VOUT_MAX_SUBPICTURES; i_subpic++ )
141         {
142             p_subpic = &p_vout->p_subpicture[i_subpic];
143
144             if( p_subpic != NULL &&
145               ( p_subpic->i_status == RESERVED_SUBPICTURE
146                 || p_subpic->i_status == READY_SUBPICTURE ) )
147             {
148                 vout_DestroySubPicture( p_vout, p_subpic );
149             }
150         }
151     }
152     if( p_vout ) vlc_object_release( p_vout );
153
154     var_DelCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
155
156     vlc_object_release( p_intf->p_sys->p_cmml_decoder );
157    
158     free( p_intf->p_sys );
159 }
160
161
162 /*****************************************************************************
163  * RunIntf: main loop
164  *****************************************************************************/
165 static void RunIntf( intf_thread_t *p_intf )
166 {
167     vout_thread_t * p_vout = NULL;
168
169     if( InitThread( p_intf ) < 0 )
170     {
171         msg_Err( p_intf, "can't initialize CMML interface" );
172         return;
173     }
174 #ifdef CMML_INTF_DEBUG
175     msg_Dbg( p_intf, "CMML intf initialized" );
176 #endif
177
178     /* if video output is dying, disassociate ourselves from it */
179     if( p_vout && p_vout->b_die )
180     {
181         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
182         vlc_object_release( p_vout );
183         p_vout = NULL;
184     }
185
186     /* Main loop */
187     while( !p_intf->b_die )
188     {
189         vlc_value_t val;
190         decoder_t *p_cmml_decoder;
191         
192         /* find a video output if we currently don't have one */
193         if( p_vout == NULL )
194         {
195             p_vout = vlc_object_find( p_intf->p_sys->p_input,
196                                       VLC_OBJECT_VOUT, FIND_CHILD );
197             if( p_vout )
198             {
199 #ifdef CMML_INTF_DEBUG
200                 msg_Dbg( p_intf, "found vout thread" );
201 #endif
202                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
203             }
204         }
205
206         vlc_mutex_lock( &p_intf->change_lock );
207
208         /*
209          * keyboard event
210          */
211         if( p_intf->p_sys->b_key_pressed )
212         {
213             vlc_value_t val;
214             int i, i_action = -1;
215             struct hotkey *p_hotkeys = p_intf->p_vlc->p_hotkeys;
216
217             /* Find action triggered by hotkey (if any) */
218             var_Get( p_intf->p_vlc, "key-pressed", &val );
219
220             /* Acknowledge that we've handled the b_key_pressed event */
221             p_intf->p_sys->b_key_pressed = VLC_FALSE;
222
223 #ifdef CMML_INTF_DEBUG
224             msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
225 #endif
226             
227             for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
228             {
229                 if( p_hotkeys[i].i_key == val.i_int )
230                     i_action = p_hotkeys[i].i_action;
231             }
232
233             /* What did the user do? */
234             if( i_action != -1 )
235             {
236                 switch( i_action )
237                 {
238                     case ACTIONID_NAV_ACTIVATE:
239                         FollowAnchor( p_intf );
240                         break;
241                     case ACTIONID_HISTORY_BACK:
242                         GoBack( p_intf );
243                         break;
244                     case ACTIONID_HISTORY_FORWARD:
245                         GoForward( p_intf );
246                         break;
247                     default:
248                         break;
249                 }
250             }
251         }       
252
253         vlc_mutex_unlock( &p_intf->change_lock );
254
255         /*
256          * Get a pending anchor description/URL from the CMML decoder
257          * and display it on screen
258          */
259         p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
260         if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
261                 == VLC_SUCCESS )
262         {
263             if( val.p_address )
264             {
265                 char *psz_description = NULL;
266                 char *psz_url = NULL;
267
268                 psz_description = val.p_address;
269
270                 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
271                         == VLC_SUCCESS )
272                 {
273                     psz_url = val.p_address;
274                 }
275
276                 if( p_vout != NULL )
277                 {
278                     if( DisplayAnchor( p_intf, p_vout, psz_description,
279                                 psz_url ) != VLC_SUCCESS )
280                     {
281                         /* text render unsuccessful: do nothing */
282                     }
283                     else
284                     {
285                         /* text render successful: clear description */
286                         val.p_address = NULL;
287                         if( var_Set( p_cmml_decoder,
288                                     "psz-current-anchor-description", val ) !=
289                                 VLC_SUCCESS )
290                         {
291                             msg_Dbg( p_intf, "reset of "
292                                     "psz-current-anchor-description failed" );
293                         }
294                         free( psz_description );
295                         psz_url = NULL;
296                     }
297                 }
298             }
299         }
300
301         /* Wait a bit */
302         msleep( INTF_IDLE_SLEEP );
303     }
304
305     /* if we're here, the video output is dying: release the vout object */
306
307     if( p_vout )
308     {
309         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
310         vlc_object_release( p_vout );
311     }
312
313     vlc_object_release( p_intf->p_sys->p_input );
314 }
315
316 /*****************************************************************************
317  * InitThread:
318  *****************************************************************************/
319 static int InitThread( intf_thread_t * p_intf )
320 {
321     /* We might need some locking here */
322     if( !p_intf->b_die )
323     {
324         input_thread_t * p_input;
325         decoder_t *p_cmml_decoder;
326
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 );
329
330 #ifdef CMML_INTF_DEBUG
331         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
332                  p_cmml_decoder, p_input );
333 #endif
334
335         /* Maybe the input just died */
336         if( p_input == NULL )
337         {
338             return VLC_EGENERIC;
339         }
340
341         vlc_mutex_lock( &p_intf->change_lock );
342
343         p_intf->p_sys->p_input = p_input;
344         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
345
346         p_intf->p_sys->b_key_pressed = VLC_FALSE;
347
348         vlc_mutex_unlock( &p_intf->change_lock );
349
350         return VLC_SUCCESS;
351     }
352     else
353     {
354         return VLC_EGENERIC;
355     }
356 }
357
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 )
363 {
364     /* TODO: handle mouse clicks on the anchor text */
365
366     return VLC_SUCCESS;
367 }
368
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 )
374 {
375     intf_thread_t *p_intf = (intf_thread_t *)p_data;
376     vlc_mutex_lock( &p_intf->change_lock );
377
378     p_intf->p_sys->b_key_pressed = VLC_TRUE;
379     
380     vlc_mutex_unlock( &p_intf->change_lock );
381
382     return VLC_SUCCESS;
383 }
384
385 /*****************************************************************************
386  * FollowAnchor: follow the current anchor being displayed to the user
387  *****************************************************************************/
388 static void FollowAnchor ( intf_thread_t *p_intf )
389 {
390     intf_sys_t *p_sys;
391     decoder_t *p_cmml_decoder;
392     char *psz_url = NULL;
393     vlc_value_t val;
394
395     msg_Dbg( p_intf, "User followed anchor" );
396
397     p_sys = p_intf->p_sys;
398     p_cmml_decoder = p_sys->p_cmml_decoder;
399
400     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
401             VLC_SUCCESS )
402     {
403         if( val.p_address ) psz_url = val.p_address;
404     }
405
406 #ifdef CMML_INTF_DEBUG
407     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
408 #endif
409
410     if( psz_url )
411     {
412         playlist_t *p_playlist;
413         playlist_item_t *p_current_item;
414         char *psz_uri_to_load;
415
416         p_playlist = (playlist_t *) vlc_object_find( p_intf, 
417                 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
418         if ( !p_playlist )
419         {
420             msg_Warn( p_intf, "can't find playlist" );
421             return;
422         }
423
424         /* Get new URL */
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 );
429 #endif
430
431         psz_uri_to_load = XURL_Concat( p_current_item->input.psz_uri,
432                                        psz_url );
433
434 #ifdef CMML_INTF_DEBUG
435         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
436 #endif
437
438         mtime_t i_seconds;
439         vlc_value_t time;
440         if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
441         {
442             msg_Dbg( p_intf, "couldn't get time from current clip" );
443             time.i_time = 0;
444         }
445         i_seconds = time.i_time / 1000000;
446 #ifdef CMML_INTF_DEBUG
447         msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
448 #endif
449
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 )
453         {
454             history_t *p_history = NULL;
455             history_item_t *p_history_item = NULL;
456             char *psz_timed_url;
457             
458             p_history = GetHistory( p_playlist );
459
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 );
464
465             if( !p_history_item )
466             {
467                 msg_Warn( p_intf, "could not initialise history item" );
468             }
469             else
470             {
471 #ifdef CMML_INTF_DEBUG
472                 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
473 #endif
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 );
479 #endif
480             }
481
482             /* free current-anchor-url */
483             free( psz_url );
484             val.p_address = NULL;
485             if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
486                     VLC_SUCCESS )
487             {
488                 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
489             }
490
491             ReplacePlaylistItem( p_playlist, psz_uri_to_load );
492         }
493         else
494         {
495             (void) browser_Open( psz_url );
496             playlist_Command( p_playlist, PLAYLIST_PAUSE, 0 );
497         }
498
499         free( psz_uri_to_load );
500
501         vlc_object_release( p_playlist );
502     }
503 }
504
505 static
506 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
507         playlist_item_t *p_current_item )
508 {
509 #ifdef CMML_INTF_USE_TIMED_URIS
510     char *psz_url = NULL;
511     char *psz_return_value = NULL;
512     char *psz_seconds = NULL;
513     int i_seconds;
514     
515     psz_url = XURL_GetWithoutFragment( p_current_item->input->psz_uri );
516
517     /* Get current time as a string */
518     if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
519         psz_url = xstrcat( psz_url, "#" );
520     else
521         psz_url = xstrcat( psz_url, "?" );
522
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 );
526     if( psz_seconds )
527     {
528         psz_url = xstrcat( psz_url, psz_seconds );
529         free( psz_seconds );
530         psz_return_value = psz_url;
531     }
532
533     return psz_return_value;
534 #else
535     void *p;
536
537     /* Suppress warning messages about unused functions */
538     p = GetTimedURIFragmentForTime; /* unused */
539     p = GetCurrentTimeInSeconds;    /* unused */
540
541     return strdup( p_current_item->input.psz_uri );
542 #endif
543 }
544
545
546 /*
547  * Get the current time, rounded down to the nearest second
548  *
549  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
550  */
551 static
552 int GetCurrentTimeInSeconds( input_thread_t *p_input )
553 {
554     vlc_value_t time;
555     mtime_t i_seconds;
556
557     var_Get( p_input, "time", &time );
558     i_seconds = time.i_time / 1000000;
559
560     return i_seconds;
561 }
562
563 static
564 char *GetTimedURIFragmentForTime( int seconds )
565 {
566     char *psz_time;
567
568     asprintf( &psz_time, "%d", seconds );
569
570     return psz_time;
571 }
572
573 static
574 void GoBack( intf_thread_t *p_intf )
575 {
576     vlc_value_t history;
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;
582
583 #ifdef CMML_INTF_DEBUG
584     msg_Dbg( p_intf, "Going back in navigation history" );
585 #endif
586
587     /* Find the playlist */
588     p_playlist = (playlist_t *) vlc_object_find( p_intf, 
589             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
590     if ( !p_playlist )
591     {
592         msg_Warn( p_intf, "can't find playlist" );
593         return;
594     }
595
596     /* Retrieve navigation history from playlist */
597     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
598         !history.p_address )
599     {
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 );
603         return;
604     }
605
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 );
611 #endif
612
613     /* Check whether we can go back in the history */
614     if( history_CanGoBack( p_history ) == VLC_FALSE )
615     {
616         msg_Warn( p_intf, "can't go back: already at beginning of history" );
617         vlc_object_release( p_playlist );
618         return;
619     }
620
621     playlist_item_t *p_current_item;
622     p_current_item = p_playlist->pp_items[p_playlist->i_index];
623
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 );
628
629     if( !p_new_history_item )
630     {
631 #ifdef CMML_INTF_DEBUG
632         msg_Dbg( p_intf, "back: could not initialise new history item" );
633 #endif
634         vlc_object_release( p_playlist );
635         return;
636     }
637
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 );
641
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 );
646 #endif
647
648     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
649     vlc_object_release( p_playlist );
650 }
651
652 static
653 void GoForward( intf_thread_t *p_intf )
654 {
655     vlc_value_t history;
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;
660
661 #ifdef CMML_INTF_DEBUG
662     msg_Dbg( p_intf, "Going forward in navigation history" );
663 #endif
664
665     /* Find the playlist */
666     p_playlist = (playlist_t *) vlc_object_find( p_intf, 
667             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
668     if ( !p_playlist )
669     {
670         msg_Warn( p_intf, "can't find playlist" );
671         return;
672     }
673
674     /* Retrieve navigation history from playlist */
675     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
676         !history.p_address )
677     {
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 );
681         return;
682     }
683
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 );
689 #endif
690
691     /* Check whether we can go forward in the history */
692     if( history_CanGoForward( p_history ) == VLC_FALSE )
693     {
694         msg_Warn( p_intf, "can't go forward: already at end of history" );
695         vlc_object_release( p_playlist );
696         return;
697     }
698
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 )
702     {
703 #ifdef CMML_INTF_DEBUG
704         msg_Dbg( p_intf, "forward: could not initialise new history item" );
705 #endif
706         vlc_object_release( p_playlist );
707         return;
708     }
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, 
712             p_current_item );
713     p_new_history_item->psz_name = p_new_history_item->psz_uri;
714
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 );
718
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 );
723 #endif
724
725     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
726     vlc_object_release( p_playlist );
727 }
728
729 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
730 {
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 );
735 }
736
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 )
744 {
745     int i_margin_h, i_margin_v;
746     mtime_t i_now;
747
748     i_margin_h = 0;
749     i_margin_v = 10;
750
751     i_now = mdate();
752
753     if( p_vout )
754     {
755         text_style_t *p_style = NULL;
756
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;
760
761         if( psz_anchor_url )
762         {
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;
767
768         }
769
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
772          * work out why. */
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 )
776         {
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 );
782 #endif
783         }
784         else
785         {
786             return VLC_EGENERIC;
787         }
788
789     }
790     else
791     {
792         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
793         return VLC_EGENERIC;
794     }
795
796     return VLC_SUCCESS;
797 }
798
799 static history_t * GetHistory( playlist_t *p_playlist )
800 {
801     vlc_value_t val;
802     history_t *p_history = NULL;
803
804     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
805     {
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 )
812         {
813             msg_Warn( p_playlist, "could not initialise history" );
814         }
815         else
816         {
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 );
822 #endif
823         }
824     }
825     else
826     {
827         p_history = val.p_address;
828 #ifdef CMML_INTF_HISTORY_DEBUG
829         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
830 #endif
831     }
832
833     return p_history;
834 }
835