]> git.sesse.net Git - vlc/blob - modules/codec/cmml/intf.c
1ecfbf0a409b32be67b2db3bc6131e86ab007cb6
[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 the VideoLAN team
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35
36 #include <stdio.h>
37
38 #ifdef HAVE_UNISTD_H
39 #    include <unistd.h>
40 #endif
41
42 #include <vlc_codec.h>
43 #include <vlc_input.h>
44 #include <vlc_interface.h>
45 #include <vlc_vout.h>
46 #include <vlc_playlist.h>
47 #include <vlc_osd.h>
48
49 #include "vlc_keys.h"
50
51 #include "browser_open.h"
52 #include "history.h"
53 #include "xstrcat.h"
54 #include "xurl.h"
55
56 #undef  CMML_INTF_USE_TIMED_URIS
57
58 #undef  CMML_INTF_DEBUG
59 #undef  CMML_INTF_HISTORY_DEBUG
60
61 /*****************************************************************************
62  * intf_sys_t: description and status of interface
63  *****************************************************************************/
64 struct intf_sys_t
65 {
66     decoder_t *         p_cmml_decoder;
67     input_thread_t *    p_input;
68
69     int                 i_key_action;
70 };
71
72 struct navigation_history_t
73 {
74     int i_history_size;
75     int i_last_item;
76 };
77
78 /*****************************************************************************
79  * Local prototypes.
80  *****************************************************************************/
81
82 int          OpenIntf               ( vlc_object_t * );
83 void         CloseIntf              ( vlc_object_t * );
84
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 * );
90
91 static void  FollowAnchor               ( intf_thread_t * );
92 static void  GoBack                     ( intf_thread_t * );
93 static void  GoForward                  ( intf_thread_t * );
94
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 * );
101
102 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
103 static char *GetTimedURIFragmentForTime ( int );
104 static int   GetCurrentTimeInSeconds    ( input_thread_t * );
105 static int   DisplayAnchor              ( intf_thread_t *, vout_thread_t *,
106                                           char *, char * );
107 static int   DisplayPendingAnchor       ( intf_thread_t *, vout_thread_t * );
108 static history_t * GetHistory           ( playlist_t * );
109 static void  ReplacePlaylistItem        ( playlist_t *, char * );
110
111 /* Exported functions */
112 static void RunIntf        ( intf_thread_t *p_intf );
113
114 /*****************************************************************************
115  * OpenIntf: initialize CMML interface
116  *****************************************************************************/
117 int OpenIntf ( vlc_object_t *p_this )
118 {
119     intf_thread_t *p_intf = (intf_thread_t *)p_this;
120
121     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
122     if( p_intf->p_sys == NULL )
123     {
124         return( 1 );
125     };
126
127     p_intf->pf_run = RunIntf;
128
129     var_AddCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
130     /* we also need to add the callback for "mouse-clicked", but do that later
131      * when we've found a p_vout */
132
133     var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
134     var_AddCallback( p_intf->p_libvlc, "browse-go-back",
135                      GoBackCallback, p_intf );
136     var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
137     var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
138                      GoForwardCallback, p_intf );
139     var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
140     var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
141                      FollowAnchorCallback, p_intf );
142
143     return( 0 );
144 }
145
146 /*****************************************************************************
147  * CloseIntf: destroy dummy interface
148  *****************************************************************************/
149 void CloseIntf ( vlc_object_t *p_this )
150 {
151     intf_thread_t * p_intf = (intf_thread_t *)p_this;
152     vout_thread_t * p_vout;
153
154 #ifdef CMML_INTF_DEBUG
155     msg_Dbg( p_intf, "freeing CMML interface" );
156 #endif
157
158     /* erase the anchor text description from the video output if it exists */
159     p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
160     if( p_vout )
161     {
162         /* enable CMML as a subtitle track */
163         spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
164         vlc_object_release( p_vout );
165     }
166
167     var_DelCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
168
169     vlc_object_release( p_intf->p_sys->p_cmml_decoder );
170
171     free( p_intf->p_sys );
172 }
173
174
175 /*****************************************************************************
176  * RunIntf: main loop
177  *****************************************************************************/
178 static void RunIntf( intf_thread_t *p_intf )
179 {
180     vout_thread_t * p_vout = NULL;
181
182     if( InitThread( p_intf ) < 0 )
183     {
184         msg_Err( p_intf, "can't initialize CMML interface" );
185         return;
186     }
187 #ifdef CMML_INTF_DEBUG
188     msg_Dbg( p_intf, "CMML intf initialized" );
189 #endif
190
191     /* Main loop */
192     while( !p_intf->b_die )
193     {
194         /* if video output is dying, disassociate ourselves from it */
195         if( p_vout && p_vout->b_die )
196         {
197             var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
198             vlc_object_release( p_vout );
199             p_vout = NULL;
200         }
201
202         /* find a video output if we currently don't have one */
203         if( p_vout == NULL )
204         {
205             p_vout = vlc_object_find( p_intf->p_sys->p_input,
206                                       VLC_OBJECT_VOUT, FIND_CHILD );
207             if( p_vout )
208             {
209 #ifdef CMML_INTF_DEBUG
210                 msg_Dbg( p_intf, "found vout thread" );
211 #endif
212                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
213             }
214         }
215
216         vlc_mutex_lock( &p_intf->change_lock );
217
218         /*
219          * keyboard event
220          */
221         switch( p_intf->p_sys->i_key_action )
222         {
223             case ACTIONID_NAV_ACTIVATE:
224                 FollowAnchor( p_intf );
225                 break;
226             case ACTIONID_HISTORY_BACK:
227                 GoBack( p_intf );
228                 break;
229             case ACTIONID_HISTORY_FORWARD:
230                 GoForward( p_intf );
231                 break;
232             default:
233                 break;
234         }
235         p_intf->p_sys->i_key_action = 0;
236         vlc_mutex_unlock( &p_intf->change_lock );
237
238         (void) DisplayPendingAnchor( p_intf, p_vout );
239
240         /* Wait a bit */
241         msleep( INTF_IDLE_SLEEP );
242     }
243
244     /* if we're here, the video output is dying: release the vout object */
245
246     if( p_vout )
247     {
248         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
249         vlc_object_release( p_vout );
250     }
251
252     vlc_object_release( p_intf->p_sys->p_input );
253 }
254
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 )
260 {
261     decoder_t *p_cmml_decoder;
262     char *psz_description = NULL;
263     char *psz_url = NULL;
264
265     intf_thread_t *p_primary_intf;
266     vlc_value_t val;
267
268     p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
269     if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
270             != VLC_SUCCESS )
271     {
272         return true;
273     }
274
275     if( !val.p_address )
276         return true;
277
278     psz_description = val.p_address;
279
280     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
281             == VLC_SUCCESS )
282     {
283         psz_url = val.p_address;
284     }
285
286     if( p_vout != NULL )
287     {
288         /* don't display anchor if main interface can display it */
289         p_primary_intf = vlc_object_find( p_intf->p_libvlc, VLC_OBJECT_INTF,
290                 FIND_CHILD );
291
292         if( p_primary_intf )
293         {
294             if( var_Get( p_primary_intf, "intf-displays-cmml-description", &val )
295                     == VLC_SUCCESS )
296             {
297                 if( val.b_bool == true )
298                 {
299                     vlc_object_release( p_primary_intf );
300                     return true;
301                 }
302             }
303
304             vlc_object_release( p_primary_intf );
305         }
306
307         /* display anchor as subtitle on-screen */
308         if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
309                 != VLC_SUCCESS )
310         {
311             /* text render unsuccessful: do nothing */
312             return false;
313         }
314
315         /* text render successful: clear description */
316         val.p_address = NULL;
317         if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
318                 != VLC_SUCCESS )
319         {
320             msg_Dbg( p_intf,
321                      "reset of psz-current-anchor-description failed" );
322         }
323         free( psz_description );
324         psz_url = NULL;
325     }
326
327     return true;
328 }
329
330
331 /*****************************************************************************
332  * InitThread:
333  *****************************************************************************/
334 static int InitThread( intf_thread_t * p_intf )
335 {
336     /* We might need some locking here */
337     if( !p_intf->b_die )
338     {
339         input_thread_t * p_input;
340         decoder_t *p_cmml_decoder;
341
342         p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
343         p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
344
345 #ifdef CMML_INTF_DEBUG
346         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
347                  p_cmml_decoder, p_input );
348 #endif
349
350         /* Maybe the input just died */
351         if( p_input == NULL )
352         {
353             return VLC_EGENERIC;
354         }
355
356         vlc_mutex_lock( &p_intf->change_lock );
357
358         p_intf->p_sys->p_input = p_input;
359         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
360
361         p_intf->p_sys->i_key_action = 0;
362
363         vlc_mutex_unlock( &p_intf->change_lock );
364
365         return VLC_SUCCESS;
366     }
367     else
368     {
369         return VLC_EGENERIC;
370     }
371 }
372
373 /*****************************************************************************
374  * MouseEvent: callback for mouse events
375  *****************************************************************************/
376 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
377                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
378 {
379     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
380     VLC_UNUSED(oldval); VLC_UNUSED(newval);
381     VLC_UNUSED(p_data);
382     /* TODO: handle mouse clicks on the anchor text */
383
384     return VLC_SUCCESS;
385 }
386
387 /*****************************************************************************
388  * KeyEvent: callback for keyboard events
389  *****************************************************************************/
390 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
391                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
392 {
393     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
394     VLC_UNUSED(oldval); VLC_UNUSED(newval);
395     intf_thread_t *p_intf = (intf_thread_t *)p_data;
396     vlc_mutex_lock( &p_intf->change_lock );
397
398     p_intf->p_sys->i_key_action = newval.i_int;
399
400     vlc_mutex_unlock( &p_intf->change_lock );
401
402     return VLC_SUCCESS;
403 }
404
405 /*****************************************************************************
406  * FollowAnchor: follow the current anchor being displayed to the user
407  *****************************************************************************/
408 static void FollowAnchor ( intf_thread_t *p_intf )
409 {
410     intf_sys_t *p_sys;
411     decoder_t *p_cmml_decoder;
412     char *psz_url = NULL;
413     vlc_value_t val;
414
415     msg_Dbg( p_intf, "User followed anchor" );
416
417     p_sys = p_intf->p_sys;
418     p_cmml_decoder = p_sys->p_cmml_decoder;
419
420     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
421             VLC_SUCCESS )
422     {
423         if( val.p_address ) psz_url = val.p_address;
424     }
425
426 #ifdef CMML_INTF_DEBUG
427     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
428 #endif
429
430     if( psz_url )
431     {
432         playlist_t *p_playlist;
433         playlist_item_t *p_current_item;
434         char *psz_uri_to_load;
435         mtime_t i_seconds;
436         vlc_value_t time;
437
438         p_playlist = (playlist_t *) vlc_object_find( p_intf,
439                 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
440         if ( !p_playlist )
441         {
442             msg_Warn( p_intf, "can't find playlist" );
443             return;
444         }
445
446         /* Get new URL */
447         p_current_item = p_playlist->status.p_item;
448         char *psz_uri = input_item_GetURI( p_current_item->p_input );
449 #ifdef CMML_INTF_DEBUG
450         msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
451 #endif
452
453         psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
454         free( psz_uri );
455
456 #ifdef CMML_INTF_DEBUG
457         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
458 #endif
459
460         if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
461         {
462             msg_Dbg( p_intf, "couldn't get time from current clip" );
463             time.i_time = 0;
464         }
465         i_seconds = time.i_time / 1000000;
466 #ifdef CMML_INTF_DEBUG
467         msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
468 #endif
469
470         /* TODO: we need a (much) more robust way of detecting whether
471          * the file's a media file ... */
472         if( strstr( psz_uri_to_load, ".anx" ) != NULL )
473         {
474             history_t *p_history = NULL;
475             history_item_t *p_history_item = NULL;
476             char *psz_timed_url;
477
478             p_history = GetHistory( p_playlist );
479
480             /* create history item */
481             psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
482             p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
483             free( psz_timed_url );
484
485             if( !p_history_item )
486             {
487                 msg_Warn( p_intf, "could not initialise history item" );
488             }
489             else
490             {
491 #ifdef CMML_INTF_DEBUG
492                 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
493 #endif
494                 history_PruneAndInsert( p_history, p_history_item );
495 #ifdef CMML_INTF_DEBUG
496                 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
497                          p_history_item, p_history_item->psz_uri );
498                 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
499 #endif
500             }
501
502             /* free current-anchor-url */
503             free( psz_url );
504             val.p_address = NULL;
505             if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
506                     VLC_SUCCESS )
507             {
508                 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
509             }
510
511             ReplacePlaylistItem( p_playlist, psz_uri_to_load );
512         }
513         else
514         {
515 #ifdef CMML_INTF_DEBUG
516             msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
517 #endif
518             (void) browser_Open( psz_url );
519             playlist_Control( p_playlist, PLAYLIST_PAUSE, true, 0 );
520         }
521
522         free( psz_uri_to_load );
523
524         vlc_object_release( p_playlist );
525     }
526 }
527
528 static
529 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
530         playlist_item_t *p_current_item )
531 {
532 #ifdef CMML_INTF_USE_TIMED_URIS
533     char *psz_url = NULL;
534     char *psz_return_value = NULL;
535     char *psz_seconds = NULL;
536     int i_seconds;
537
538     char *psz_uri = input_item_GetURI( p_current_item->p_input );
539     psz_url = XURL_GetWithoutFragment( psz_uri );
540     free( psz_uri );
541
542     /* Get current time as a string */
543     if( XURL_IsFileURL( psz_url ) == true )
544         psz_url = xstrcat( psz_url, "#" );
545     else
546         psz_url = xstrcat( psz_url, "?" );
547
548     /* jump back to 2 seconds before where we are now */
549     i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
550     psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
551     if( psz_seconds )
552     {
553         psz_url = xstrcat( psz_url, psz_seconds );
554         free( psz_seconds );
555         psz_return_value = psz_url;
556     }
557
558     return psz_return_value;
559 #else
560     VLC_UNUSED(p_intf);
561     void *p;
562
563     /* Suppress warning messages about unused functions */
564     p = GetTimedURIFragmentForTime; /* unused */
565     p = GetCurrentTimeInSeconds;    /* unused */
566
567     return input_item_GetURI( p_current_item->p_input );
568 #endif
569 }
570
571
572 /*
573  * Get the current time, rounded down to the nearest second
574  *
575  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
576  */
577 static
578 int GetCurrentTimeInSeconds( input_thread_t *p_input )
579 {
580     vlc_value_t time;
581     mtime_t i_seconds;
582
583     var_Get( p_input, "time", &time );
584     i_seconds = time.i_time / 1000000;
585     return i_seconds;
586 }
587
588 static
589 char *GetTimedURIFragmentForTime( int seconds )
590 {
591     char *psz_time;
592
593     asprintf( &psz_time, "%d", seconds );
594     return psz_time;
595 }
596
597 static
598 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
599                     vlc_value_t oldval, vlc_value_t newval, void *p_data )
600 {
601     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
602     VLC_UNUSED(oldval); VLC_UNUSED(newval);
603     intf_thread_t *p_intf = (intf_thread_t *) p_data;
604     GoBack( p_intf );
605     return VLC_SUCCESS;
606 }
607
608 static
609 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
610                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
611 {
612     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
613     VLC_UNUSED(oldval); VLC_UNUSED(newval);
614     intf_thread_t *p_intf = (intf_thread_t *) p_data;
615     GoForward( p_intf );
616     return VLC_SUCCESS;
617 }
618
619 static
620 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
621                           vlc_value_t oldval, vlc_value_t newval,
622                           void *p_data )
623 {
624     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
625     VLC_UNUSED(oldval); VLC_UNUSED(newval);
626     intf_thread_t *p_intf = (intf_thread_t *) p_data;
627     FollowAnchor( p_intf );
628     return VLC_SUCCESS;
629 }
630
631 static
632 void GoBack( intf_thread_t *p_intf )
633 {
634     vlc_value_t history;
635     history_t *p_history = NULL;
636     history_item_t *p_history_item = NULL;
637     history_item_t *p_new_history_item = NULL;
638     playlist_t *p_playlist = NULL;
639     char *psz_timed_url = NULL;
640     playlist_item_t *p_current_item;
641
642 #ifdef CMML_INTF_DEBUG
643     msg_Dbg( p_intf, "Going back in navigation history" );
644 #endif
645
646     /* Find the playlist */
647     p_playlist = (playlist_t *) vlc_object_find( p_intf,
648             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
649     if ( !p_playlist )
650     {
651         msg_Warn( p_intf, "can't find playlist" );
652         return;
653     }
654
655     /* Retrieve navigation history from playlist */
656     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
657         !history.p_address )
658     {
659         /* History doesn't exist yet: ignore user's request */
660         msg_Warn( p_intf, "can't go back: no history exists yet" );
661         vlc_object_release( p_playlist );
662         return;
663     }
664
665     p_history = history.p_address;
666 #ifdef CMML_INTF_DEBUG
667     msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
668     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
669              p_history->p_xarray );
670 #endif
671
672     /* Check whether we can go back in the history */
673     if( history_CanGoBack( p_history ) == false )
674     {
675         msg_Warn( p_intf, "can't go back: already at beginning of history" );
676         vlc_object_release( p_playlist );
677         return;
678     }
679
680     p_current_item = p_playlist->status.p_item;
681
682     /* Save the currently-playing media in a new history item */
683     psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
684     p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
685     free( psz_timed_url );
686
687     if( !p_new_history_item )
688     {
689 #ifdef CMML_INTF_DEBUG
690         msg_Dbg( p_intf, "back: could not initialise new history item" );
691 #endif
692         vlc_object_release( p_playlist );
693         return;
694     }
695
696     /* Go back in the history, saving the currently-playing item */
697     (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
698     p_history_item = history_Item( p_history );
699
700 #ifdef CMML_INTF_DEBUG
701     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
702     msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
703     msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
704 #endif
705
706     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
707     vlc_object_release( p_playlist );
708 }
709
710 static
711 void GoForward( intf_thread_t *p_intf )
712 {
713     vlc_value_t history;
714     history_t *p_history = NULL;
715     history_item_t *p_history_item = NULL;
716     history_item_t *p_new_history_item = NULL;
717     playlist_t *p_playlist = NULL;
718     playlist_item_t *p_current_item;
719
720 #ifdef CMML_INTF_DEBUG
721     msg_Dbg( p_intf, "Going forward in navigation history" );
722 #endif
723
724     /* Find the playlist */
725     p_playlist = (playlist_t *) vlc_object_find( p_intf,
726             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
727     if ( !p_playlist )
728     {
729         msg_Warn( p_intf, "can't find playlist" );
730         return;
731     }
732
733     /* Retrieve navigation history from playlist */
734     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
735         !history.p_address )
736     {
737         /* History doesn't exist yet: ignore user's request */
738         msg_Warn( p_intf, "can't go back: no history exists yet" );
739         vlc_object_release( p_playlist );
740         return;
741     }
742
743     p_history = history.p_address;
744 #ifdef CMML_INTF_DEBUG
745     msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
746     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
747              p_history->p_xarray );
748 #endif
749
750     /* Check whether we can go forward in the history */
751     if( history_CanGoForward( p_history ) == false )
752     {
753         msg_Warn( p_intf, "can't go forward: already at end of history" );
754         vlc_object_release( p_playlist );
755         return;
756     }
757
758     /* Save the currently-playing media in a new history item */
759     p_new_history_item = malloc( sizeof(history_item_t) );
760     if( !p_new_history_item )
761     {
762 #ifdef CMML_INTF_DEBUG
763         msg_Dbg( p_intf, "forward: could not initialise new history item" );
764 #endif
765         vlc_object_release( p_playlist );
766         return;
767     }
768     p_current_item = p_playlist->status.p_item;
769     p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
770             p_current_item );
771     p_new_history_item->psz_name = p_new_history_item->psz_uri;
772
773     /* Go forward in the history, saving the currently-playing item */
774     (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
775     p_history_item = history_Item( p_history );
776
777 #ifdef CMML_INTF_DEBUG
778     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
779     msg_Dbg( p_intf, "got next history item: %p", p_history_item );
780     msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
781 #endif
782
783     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
784     vlc_object_release( p_playlist );
785 }
786
787 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
788 {
789     playlist_Stop( p_playlist );
790     (void) playlist_Add( p_playlist, psz_uri, psz_uri,
791                          PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, true /* FIXME: p_playlist->status.i_index */,
792                          false);
793 }
794
795 /****************************************************************************
796  * DisplayAnchor: displays an anchor on the given video output
797  ****************************************************************************/
798 static int DisplayAnchor( intf_thread_t *p_intf,
799         vout_thread_t *p_vout,
800         char *psz_anchor_description,
801         char *psz_anchor_url )
802 {
803     int i_margin_h, i_margin_v;
804     mtime_t i_now;
805
806     i_margin_h = 0;
807     i_margin_v = 10;
808
809     i_now = mdate();
810
811     if( p_vout )
812     {
813         if( psz_anchor_url )
814         {
815             /* Should display subtitle underlined and in blue, but it looks
816              * like VLC doesn't implement any text styles yet.  D'oh! */
817             // p_style = &blue_with_underline;
818
819         }
820
821         /* TODO: p_subpicture doesn't have the proper i_x and i_y
822          * coordinates.  Need to look at the subpicture display system to
823          * work out why. */
824         if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
825                 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
826                 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
827         {
828             /* Displayed successfully */
829         }
830         else
831         {
832             return VLC_EGENERIC;
833         }
834     }
835     else
836     {
837         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
838         return VLC_EGENERIC;
839     }
840
841     return VLC_SUCCESS;
842 }
843
844 static history_t * GetHistory( playlist_t *p_playlist )
845 {
846     vlc_value_t val;
847     history_t *p_history = NULL;
848
849     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
850     {
851         /* history doesn't exist yet: need to create it */
852         history_t *new_history = history_New();
853         val.p_address = new_history;
854         var_Create( p_playlist, "navigation-history",
855                 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
856         if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
857         {
858             msg_Warn( p_playlist, "could not initialise history" );
859         }
860         else
861         {
862             p_history = new_history;
863 #ifdef CMML_INTF_HISTORY_DEBUG
864             msg_Dbg( p_playlist, "nav history created at %p", new_history );
865             msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
866                      p_history->i_index, p_history->p_xarray );
867 #endif
868         }
869     }
870     else
871     {
872         p_history = val.p_address;
873 #ifdef CMML_INTF_HISTORY_DEBUG
874         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
875 #endif
876     }
877
878     return p_history;
879 }
880