]> git.sesse.net Git - vlc/blob - modules/codec/cmml/intf.c
18463d5f2b3f6098871b5f0a9ab8c9692ae39e6f
[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_interface.h>
44 #include <vlc_playlist.h>
45 #include <vlc_osd.h>
46
47 #include <vlc_keys.h>
48
49 #include "browser_open.h"
50 #include "history.h"
51 #include "xstrcat.h"
52 #include "xurl.h"
53
54 #undef CMML_INTF_USE_TIMED_URIS
55
56 #undef  CMML_INTF_DEBUG
57 #undef  CMML_INTF_HISTORY_DEBUG
58
59 /*****************************************************************************
60  * intf_sys_t: description and status of interface
61  *****************************************************************************/
62 struct intf_sys_t
63 {
64     decoder_t *         p_cmml_decoder;
65     input_thread_t *    p_input;
66
67     int                 i_key_action;
68 };
69
70 struct navigation_history_t
71 {
72     int i_history_size;
73     int i_last_item;
74 };
75
76 /*****************************************************************************
77  * Local prototypes.
78  *****************************************************************************/
79
80 int          OpenIntf               ( vlc_object_t * );
81 void         CloseIntf              ( vlc_object_t * );
82
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 * );
88
89 static void  FollowAnchor               ( intf_thread_t * );
90 static void  GoBack                     ( intf_thread_t * );
91 static void  GoForward                  ( intf_thread_t * );
92
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 * );
99
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 );
104 #endif
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         return VLC_ENOMEM;
124
125     p_intf->pf_run = RunIntf;
126
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 */
130
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 );
140
141     return VLC_SUCCESS;
142 }
143
144 /*****************************************************************************
145  * CloseIntf: destroy dummy interface
146  *****************************************************************************/
147 void CloseIntf ( vlc_object_t *p_this )
148 {
149     intf_thread_t * p_intf = (intf_thread_t *)p_this;
150     vout_thread_t * p_vout;
151
152 #ifdef CMML_INTF_DEBUG
153     msg_Dbg( p_intf, "freeing CMML interface" );
154 #endif
155
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 );
158     if( p_vout )
159     {
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 );
163     }
164
165     var_DelCallback( p_intf->p_libvlc, "key-action", KeyEvent, p_intf );
166
167     vlc_object_release( p_intf->p_sys->p_cmml_decoder );
168
169     free( p_intf->p_sys );
170 }
171
172
173 /*****************************************************************************
174  * RunIntf: main loop
175  *****************************************************************************/
176 static void RunIntf( intf_thread_t *p_intf )
177 {
178     int canc = vlc_savecancel();
179     vout_thread_t * p_vout = NULL;
180
181     if( InitThread( p_intf ) < 0 )
182     {
183         msg_Err( p_intf, "can't initialize CMML interface" );
184         return;
185     }
186 #ifdef CMML_INTF_DEBUG
187     msg_Dbg( p_intf, "CMML intf initialized" );
188 #endif
189
190     /* Main loop */
191     while( vlc_object_alive (p_intf) )
192     {
193         /* if video output is dying, disassociate ourselves from it */
194         if( p_vout && !vlc_object_alive (p_vout) )
195         {
196             var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
197             vlc_object_release( p_vout );
198             p_vout = NULL;
199         }
200
201         /* find a video output if we currently don't have one */
202         if( p_vout == NULL )
203         {
204             p_vout = vlc_object_find( p_intf->p_sys->p_input,
205                                       VLC_OBJECT_VOUT, FIND_CHILD );
206             if( p_vout )
207             {
208 #ifdef CMML_INTF_DEBUG
209                 msg_Dbg( p_intf, "found vout thread" );
210 #endif
211                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
212             }
213         }
214
215         vlc_mutex_lock( &p_intf->change_lock );
216
217         /*
218          * keyboard event
219          */
220         switch( p_intf->p_sys->i_key_action )
221         {
222             case ACTIONID_NAV_ACTIVATE:
223                 FollowAnchor( p_intf );
224                 break;
225             case ACTIONID_HISTORY_BACK:
226                 GoBack( p_intf );
227                 break;
228             case ACTIONID_HISTORY_FORWARD:
229                 GoForward( p_intf );
230                 break;
231             default:
232                 break;
233         }
234         p_intf->p_sys->i_key_action = 0;
235         vlc_mutex_unlock( &p_intf->change_lock );
236
237         (void) DisplayPendingAnchor( p_intf, p_vout );
238
239         /* Wait a bit */
240         msleep( INTF_IDLE_SLEEP );
241     }
242
243     /* if we're here, the video output is dying: release the vout object */
244
245     if( p_vout )
246     {
247         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
248         vlc_object_release( p_vout );
249     }
250
251     vlc_object_release( p_intf->p_sys->p_input );
252     vlc_restorecancel( canc );
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         /* display anchor as subtitle on-screen */
289         if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
290                 != VLC_SUCCESS )
291         {
292             /* text render unsuccessful: do nothing */
293             return false;
294         }
295
296         /* text render successful: clear description */
297         val.p_address = NULL;
298         if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
299                 != VLC_SUCCESS )
300         {
301             msg_Dbg( p_intf,
302                      "reset of psz-current-anchor-description failed" );
303         }
304         free( psz_description );
305         psz_url = NULL;
306     }
307
308     return true;
309 }
310
311
312 /*****************************************************************************
313  * InitThread:
314  *****************************************************************************/
315 static int InitThread( intf_thread_t * p_intf )
316 {
317     /* We might need some locking here */
318     if( vlc_object_alive (p_intf) )
319     {
320         input_thread_t * p_input;
321         decoder_t *p_cmml_decoder;
322
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 );
325
326 #ifdef CMML_INTF_DEBUG
327         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
328                  p_cmml_decoder, p_input );
329 #endif
330
331         /* Maybe the input just died */
332         if( p_input == NULL )
333         {
334             return VLC_EGENERIC;
335         }
336
337         vlc_mutex_lock( &p_intf->change_lock );
338
339         p_intf->p_sys->p_input = p_input;
340         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
341
342         p_intf->p_sys->i_key_action = 0;
343
344         vlc_mutex_unlock( &p_intf->change_lock );
345
346         return VLC_SUCCESS;
347     }
348     else
349     {
350         return VLC_EGENERIC;
351     }
352 }
353
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 )
359 {
360     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
361     VLC_UNUSED(oldval); VLC_UNUSED(newval);
362     VLC_UNUSED(p_data);
363     /* TODO: handle mouse clicks on the anchor text */
364
365     return VLC_SUCCESS;
366 }
367
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 )
373 {
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 );
378
379     p_intf->p_sys->i_key_action = newval.i_int;
380
381     vlc_mutex_unlock( &p_intf->change_lock );
382
383     return VLC_SUCCESS;
384 }
385
386 /*****************************************************************************
387  * FollowAnchor: follow the current anchor being displayed to the user
388  *****************************************************************************/
389 static void FollowAnchor ( intf_thread_t *p_intf )
390 {
391     intf_sys_t *p_sys;
392     decoder_t *p_cmml_decoder;
393     char *psz_url = NULL;
394     vlc_value_t val;
395
396     msg_Dbg( p_intf, "User followed anchor" );
397
398     p_sys = p_intf->p_sys;
399     p_cmml_decoder = p_sys->p_cmml_decoder;
400
401     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
402             VLC_SUCCESS )
403     {
404         if( val.p_address ) psz_url = val.p_address;
405     }
406
407 #ifdef CMML_INTF_DEBUG
408     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
409 #endif
410
411     if( psz_url )
412     {
413         playlist_t *p_playlist;
414         playlist_item_t *p_current_item;
415         char *psz_uri_to_load;
416         mtime_t i_seconds;
417         vlc_value_t time;
418
419         p_playlist = pl_Hold( p_intf );
420
421         /* Get new URL */
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 );
426 #endif
427
428         psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
429         free( psz_uri );
430
431 #ifdef CMML_INTF_DEBUG
432         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
433 #endif
434
435         if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
436         {
437             msg_Dbg( p_intf, "couldn't get time from current clip" );
438             time.i_time = 0;
439         }
440         i_seconds = time.i_time / 1000000;
441 #ifdef CMML_INTF_DEBUG
442         msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
443 #endif
444
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 )
448         {
449             history_t *p_history = NULL;
450             history_item_t *p_history_item = NULL;
451             char *psz_timed_url;
452
453             p_history = GetHistory( p_playlist );
454
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 );
459
460             if( !p_history_item )
461             {
462                 msg_Warn( p_intf, "could not initialise history item" );
463             }
464             else
465             {
466 #ifdef CMML_INTF_DEBUG
467                 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
468 #endif
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 );
474 #endif
475             }
476
477             /* free current-anchor-url */
478             free( psz_url );
479             val.p_address = NULL;
480             if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
481                     VLC_SUCCESS )
482             {
483                 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
484             }
485
486             ReplacePlaylistItem( p_playlist, psz_uri_to_load );
487         }
488         else
489         {
490 #ifdef CMML_INTF_DEBUG
491             msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
492 #endif
493             (void) browser_Open( psz_url );
494             playlist_Control( p_playlist, PLAYLIST_PAUSE, pl_Unlocked, 0 );
495         }
496
497         free( psz_uri_to_load );
498
499         vlc_object_release( p_playlist );
500     }
501 }
502
503 static
504 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
505         playlist_item_t *p_current_item )
506 {
507 #ifdef CMML_INTF_USE_TIMED_URIS
508     char *psz_url = NULL;
509     char *psz_return_value = NULL;
510     char *psz_seconds = NULL;
511     int i_seconds;
512
513     char *psz_uri = input_item_GetURI( p_current_item->p_input );
514     psz_url = XURL_GetWithoutFragment( psz_uri );
515     free( psz_uri );
516
517     /* Get current time as a string */
518     if( XURL_IsFileURL( psz_url ) == 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     VLC_UNUSED(p_intf);
536
537     return input_item_GetURI( p_current_item->p_input );
538 #endif
539 }
540
541
542
543 #ifdef CMML_INTF_USE_TIMED_URIS
544 /*
545  * Get the current time, rounded down to the nearest second
546  *
547  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
548  */
549 static
550 int GetCurrentTimeInSeconds( input_thread_t *p_input )
551 {
552     vlc_value_t time;
553     mtime_t i_seconds;
554
555     var_Get( p_input, "time", &time );
556     i_seconds = time.i_time / 1000000;
557     return i_seconds;
558 }
559
560 static
561 char *GetTimedURIFragmentForTime( int seconds )
562 {
563     char *psz_time;
564
565     if( asprintf( &psz_time, "%d", seconds ) == -1 )
566         return NULL;
567     return psz_time;
568 }
569 #endif
570
571 static
572 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
573                     vlc_value_t oldval, vlc_value_t newval, void *p_data )
574 {
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;
578     GoBack( p_intf );
579     return VLC_SUCCESS;
580 }
581
582 static
583 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
584                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
585 {
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;
589     GoForward( p_intf );
590     return VLC_SUCCESS;
591 }
592
593 static
594 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
595                           vlc_value_t oldval, vlc_value_t newval,
596                           void *p_data )
597 {
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 );
602     return VLC_SUCCESS;
603 }
604
605 static
606 void GoBack( intf_thread_t *p_intf )
607 {
608     vlc_value_t history;
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;
615
616 #ifdef CMML_INTF_DEBUG
617     msg_Dbg( p_intf, "Going back in navigation history" );
618 #endif
619
620     /* Find the playlist */
621     p_playlist = pl_Hold( p_intf );
622
623     /* Retrieve navigation history from playlist */
624     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
625         !history.p_address )
626     {
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 );
630         return;
631     }
632
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 );
638 #endif
639
640     /* Check whether we can go back in the history */
641     if( history_CanGoBack( p_history ) == false )
642     {
643         msg_Warn( p_intf, "can't go back: already at beginning of history" );
644         vlc_object_release( p_playlist );
645         return;
646     }
647
648     p_current_item = playlist_CurrentPlayingItem( p_playlist );
649
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 );
654
655     if( !p_new_history_item )
656     {
657 #ifdef CMML_INTF_DEBUG
658         msg_Dbg( p_intf, "back: could not initialise new history item" );
659 #endif
660         vlc_object_release( p_playlist );
661         return;
662     }
663
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 );
667
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 );
672 #endif
673
674     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
675     vlc_object_release( p_playlist );
676 }
677
678 static
679 void GoForward( intf_thread_t *p_intf )
680 {
681     vlc_value_t history;
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;
687
688 #ifdef CMML_INTF_DEBUG
689     msg_Dbg( p_intf, "Going forward in navigation history" );
690 #endif
691
692     /* Find the playlist */
693     p_playlist = pl_Hold( p_intf );
694
695     /* Retrieve navigation history from playlist */
696     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
697         !history.p_address )
698     {
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 );
702         return;
703     }
704
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 );
710 #endif
711
712     /* Check whether we can go forward in the history */
713     if( history_CanGoForward( p_history ) == false )
714     {
715         msg_Warn( p_intf, "can't go forward: already at end of history" );
716         vlc_object_release( p_playlist );
717         return;
718     }
719
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 )
723     {
724 #ifdef CMML_INTF_DEBUG
725         msg_Dbg( p_intf, "forward: could not initialise new history item" );
726 #endif
727         vlc_object_release( p_playlist );
728         return;
729     }
730     p_current_item = playlist_CurrentPlayingItem( p_playlist );
731     p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
732             p_current_item );
733     p_new_history_item->psz_name = p_new_history_item->psz_uri;
734
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 );
738
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 );
743 #endif
744
745     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
746     vlc_object_release( p_playlist );
747 }
748
749 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
750 {
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 */,
754                          false);
755 }
756
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 )
764 {
765     int i_margin_h, i_margin_v;
766     mtime_t i_now;
767
768     i_margin_h = 0;
769     i_margin_v = 10;
770
771     i_now = mdate();
772
773     if( p_vout )
774     {
775         if( psz_anchor_url )
776         {
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;
780
781         }
782
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
785          * work out why. */
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 )
789         {
790             /* Displayed successfully */
791         }
792         else
793         {
794             return VLC_EGENERIC;
795         }
796     }
797     else
798     {
799         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
800         return VLC_EGENERIC;
801     }
802
803     return VLC_SUCCESS;
804 }
805
806 static history_t * GetHistory( playlist_t *p_playlist )
807 {
808     vlc_value_t val;
809     history_t *p_history = NULL;
810
811     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
812     {
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 )
819         {
820             msg_Warn( p_playlist, "could not initialise history" );
821         }
822         else
823         {
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 );
829 #endif
830         }
831     }
832     else
833     {
834         p_history = val.p_address;
835 #ifdef CMML_INTF_HISTORY_DEBUG
836         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
837 #endif
838     }
839
840     return p_history;
841 }
842