]> git.sesse.net Git - vlc/blob - modules/codec/cmml/intf.c
CMML: privatize callback lock
[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     vlc_mutex_t         lock;
65     decoder_t *         p_cmml_decoder;
66     input_thread_t *    p_input;
67
68     int                 i_key_action;
69 };
70
71 struct navigation_history_t
72 {
73     int i_history_size;
74     int i_last_item;
75 };
76
77 /*****************************************************************************
78  * Local prototypes.
79  *****************************************************************************/
80
81 int          OpenIntf               ( vlc_object_t * );
82 void         CloseIntf              ( vlc_object_t * );
83
84 static int   InitThread                 ( intf_thread_t * );
85 static int   MouseEvent                 ( vlc_object_t *, char const *,
86                                           vlc_value_t, vlc_value_t, void * );
87 static int   KeyEvent                   ( vlc_object_t *, char const *,
88                                           vlc_value_t, vlc_value_t, void * );
89
90 static void  FollowAnchor               ( intf_thread_t * );
91 static void  GoBack                     ( intf_thread_t * );
92 static void  GoForward                  ( intf_thread_t * );
93
94 static int   FollowAnchorCallback       ( vlc_object_t *, char const *,
95                                           vlc_value_t, vlc_value_t, void * );
96 static int   GoBackCallback             ( vlc_object_t *, char const *,
97                                           vlc_value_t, vlc_value_t, void * );
98 static int   GoForwardCallback          ( vlc_object_t *, char const *,
99                                           vlc_value_t, vlc_value_t, void * );
100
101 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
102 #ifdef CMML_INTF_USE_TIMED_URIS
103 static int   GetCurrentTimeInSeconds    ( input_thread_t * );
104 static char *GetTimedURIFragmentForTime ( int );
105 #endif
106 static int   DisplayAnchor              ( intf_thread_t *, vout_thread_t *,
107                                           char *, char * );
108 static int   DisplayPendingAnchor       ( intf_thread_t *, vout_thread_t * );
109 static history_t * GetHistory           ( playlist_t * );
110 static void  ReplacePlaylistItem        ( playlist_t *, char * );
111
112 /* Exported functions */
113 static void RunIntf        ( intf_thread_t *p_intf );
114
115 /*****************************************************************************
116  * OpenIntf: initialize CMML interface
117  *****************************************************************************/
118 int OpenIntf ( vlc_object_t *p_this )
119 {
120     intf_thread_t *p_intf = (intf_thread_t *)p_this;
121
122     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
123     if( p_intf->p_sys == NULL )
124         return VLC_ENOMEM;
125
126     p_intf->pf_run = RunIntf;
127     vlc_mutex_init( &p_intf->p_sys->lock );
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 VLC_SUCCESS;
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     vlc_mutex_destroy( &p_intf->p_sys->lock );
172     free( p_intf->p_sys );
173 }
174
175
176 /*****************************************************************************
177  * RunIntf: main loop
178  *****************************************************************************/
179 static void RunIntf( intf_thread_t *p_intf )
180 {
181     int canc = vlc_savecancel();
182     vout_thread_t * p_vout = NULL;
183
184     if( InitThread( p_intf ) < 0 )
185     {
186         msg_Err( p_intf, "can't initialize CMML interface" );
187         return;
188     }
189 #ifdef CMML_INTF_DEBUG
190     msg_Dbg( p_intf, "CMML intf initialized" );
191 #endif
192
193     /* Main loop */
194     while( vlc_object_alive (p_intf) )
195     {
196         /* if video output is dying, disassociate ourselves from it */
197         if( p_vout && !vlc_object_alive (p_vout) )
198         {
199             var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
200             vlc_object_release( p_vout );
201             p_vout = NULL;
202         }
203
204         /* find a video output if we currently don't have one */
205         if( p_vout == NULL )
206         {
207             p_vout = vlc_object_find( p_intf->p_sys->p_input,
208                                       VLC_OBJECT_VOUT, FIND_CHILD );
209             if( p_vout )
210             {
211 #ifdef CMML_INTF_DEBUG
212                 msg_Dbg( p_intf, "found vout thread" );
213 #endif
214                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
215             }
216         }
217
218         vlc_mutex_lock( &p_intf->p_sys->lock );
219
220         /*
221          * keyboard event
222          */
223         switch( p_intf->p_sys->i_key_action )
224         {
225             case ACTIONID_NAV_ACTIVATE:
226                 FollowAnchor( p_intf );
227                 break;
228             case ACTIONID_HISTORY_BACK:
229                 GoBack( p_intf );
230                 break;
231             case ACTIONID_HISTORY_FORWARD:
232                 GoForward( p_intf );
233                 break;
234             default:
235                 break;
236         }
237         p_intf->p_sys->i_key_action = 0;
238         vlc_mutex_unlock( &p_intf->p_sys->lock );
239
240         (void) DisplayPendingAnchor( p_intf, p_vout );
241
242         /* Wait a bit */
243         msleep( INTF_IDLE_SLEEP );
244     }
245
246     /* if we're here, the video output is dying: release the vout object */
247
248     if( p_vout )
249     {
250         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
251         vlc_object_release( p_vout );
252     }
253
254     vlc_object_release( p_intf->p_sys->p_input );
255     vlc_restorecancel( canc );
256 }
257
258 /*****************************************************************************
259  * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
260  * decoder and display it on screen
261  *****************************************************************************/
262 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
263 {
264     decoder_t *p_cmml_decoder;
265     char *psz_description = NULL;
266     char *psz_url = NULL;
267
268     intf_thread_t *p_primary_intf;
269     vlc_value_t val;
270
271     p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
272     if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
273             != VLC_SUCCESS )
274     {
275         return true;
276     }
277
278     if( !val.p_address )
279         return true;
280
281     psz_description = val.p_address;
282
283     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
284             == VLC_SUCCESS )
285     {
286         psz_url = val.p_address;
287     }
288
289     if( p_vout != NULL )
290     {
291         /* display anchor as subtitle on-screen */
292         if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
293                 != VLC_SUCCESS )
294         {
295             /* text render unsuccessful: do nothing */
296             return false;
297         }
298
299         /* text render successful: clear description */
300         val.p_address = NULL;
301         if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
302                 != VLC_SUCCESS )
303         {
304             msg_Dbg( p_intf,
305                      "reset of psz-current-anchor-description failed" );
306         }
307         free( psz_description );
308         psz_url = NULL;
309     }
310
311     return true;
312 }
313
314
315 /*****************************************************************************
316  * InitThread:
317  *****************************************************************************/
318 static int InitThread( intf_thread_t * p_intf )
319 {
320     /* We might need some locking here */
321     if( vlc_object_alive (p_intf) )
322     {
323         input_thread_t * p_input;
324         decoder_t *p_cmml_decoder;
325
326         p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
327         p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
328
329 #ifdef CMML_INTF_DEBUG
330         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
331                  p_cmml_decoder, p_input );
332 #endif
333
334         /* Maybe the input just died */
335         if( p_input == NULL )
336         {
337             return VLC_EGENERIC;
338         }
339
340         vlc_mutex_lock( &p_intf->p_sys->lock );
341
342         p_intf->p_sys->p_input = p_input;
343         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
344
345         p_intf->p_sys->i_key_action = 0;
346
347         vlc_mutex_unlock( &p_intf->p_sys->lock );
348
349         return VLC_SUCCESS;
350     }
351     else
352     {
353         return VLC_EGENERIC;
354     }
355 }
356
357 /*****************************************************************************
358  * MouseEvent: callback for mouse events
359  *****************************************************************************/
360 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
361                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
362 {
363     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
364     VLC_UNUSED(oldval); VLC_UNUSED(newval);
365     VLC_UNUSED(p_data);
366     /* TODO: handle mouse clicks on the anchor text */
367
368     return VLC_SUCCESS;
369 }
370
371 /*****************************************************************************
372  * KeyEvent: callback for keyboard events
373  *****************************************************************************/
374 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
375                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
376 {
377     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
378     VLC_UNUSED(oldval); VLC_UNUSED(newval);
379     intf_thread_t *p_intf = (intf_thread_t *)p_data;
380
381
382     vlc_mutex_lock( &p_intf->p_sys->lock );
383     /* FIXME: key presses might get lost here... */
384     p_intf->p_sys->i_key_action = newval.i_int;
385
386     vlc_mutex_unlock( &p_intf->p_sys->lock );
387
388     return VLC_SUCCESS;
389 }
390
391 /*****************************************************************************
392  * FollowAnchor: follow the current anchor being displayed to the user
393  *****************************************************************************/
394 static void FollowAnchor ( intf_thread_t *p_intf )
395 {
396     intf_sys_t *p_sys;
397     decoder_t *p_cmml_decoder;
398     char *psz_url = NULL;
399     vlc_value_t val;
400
401     msg_Dbg( p_intf, "User followed anchor" );
402
403     p_sys = p_intf->p_sys;
404     p_cmml_decoder = p_sys->p_cmml_decoder;
405
406     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
407             VLC_SUCCESS )
408     {
409         if( val.p_address ) psz_url = val.p_address;
410     }
411
412 #ifdef CMML_INTF_DEBUG
413     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
414 #endif
415
416     if( psz_url )
417     {
418         playlist_t *p_playlist;
419         playlist_item_t *p_current_item;
420         char *psz_uri_to_load;
421         mtime_t i_seconds;
422         vlc_value_t time;
423
424         p_playlist = pl_Hold( p_intf );
425
426         /* Get new URL */
427         p_current_item = playlist_CurrentPlayingItem( p_playlist );
428         char *psz_uri = input_item_GetURI( p_current_item->p_input );
429 #ifdef CMML_INTF_DEBUG
430         msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
431 #endif
432
433         psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
434         free( psz_uri );
435
436 #ifdef CMML_INTF_DEBUG
437         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
438 #endif
439
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 #ifdef CMML_INTF_DEBUG
496             msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
497 #endif
498             (void) browser_Open( psz_url );
499             playlist_Control( p_playlist, PLAYLIST_PAUSE, pl_Unlocked, 0 );
500         }
501
502         free( psz_uri_to_load );
503
504         vlc_object_release( p_playlist );
505     }
506 }
507
508 static
509 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
510         playlist_item_t *p_current_item )
511 {
512 #ifdef CMML_INTF_USE_TIMED_URIS
513     char *psz_url = NULL;
514     char *psz_return_value = NULL;
515     char *psz_seconds = NULL;
516     int i_seconds;
517
518     char *psz_uri = input_item_GetURI( p_current_item->p_input );
519     psz_url = XURL_GetWithoutFragment( psz_uri );
520     free( psz_uri );
521
522     /* Get current time as a string */
523     if( XURL_IsFileURL( psz_url ) == true )
524         psz_url = xstrcat( psz_url, "#" );
525     else
526         psz_url = xstrcat( psz_url, "?" );
527
528     /* jump back to 2 seconds before where we are now */
529     i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
530     psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
531     if( psz_seconds )
532     {
533         psz_url = xstrcat( psz_url, psz_seconds );
534         free( psz_seconds );
535         psz_return_value = psz_url;
536     }
537
538     return psz_return_value;
539 #else
540     VLC_UNUSED(p_intf);
541
542     return input_item_GetURI( p_current_item->p_input );
543 #endif
544 }
545
546
547
548 #ifdef CMML_INTF_USE_TIMED_URIS
549 /*
550  * Get the current time, rounded down to the nearest second
551  *
552  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
553  */
554 static
555 int GetCurrentTimeInSeconds( input_thread_t *p_input )
556 {
557     vlc_value_t time;
558     mtime_t i_seconds;
559
560     var_Get( p_input, "time", &time );
561     i_seconds = time.i_time / 1000000;
562     return i_seconds;
563 }
564
565 static
566 char *GetTimedURIFragmentForTime( int seconds )
567 {
568     char *psz_time;
569
570     if( asprintf( &psz_time, "%d", seconds ) == -1 )
571         return NULL;
572     return psz_time;
573 }
574 #endif
575
576 static
577 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
578                     vlc_value_t oldval, vlc_value_t newval, void *p_data )
579 {
580     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
581     VLC_UNUSED(oldval); VLC_UNUSED(newval);
582     intf_thread_t *p_intf = (intf_thread_t *) p_data;
583     GoBack( p_intf );
584     return VLC_SUCCESS;
585 }
586
587 static
588 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
589                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
590 {
591     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
592     VLC_UNUSED(oldval); VLC_UNUSED(newval);
593     intf_thread_t *p_intf = (intf_thread_t *) p_data;
594     GoForward( p_intf );
595     return VLC_SUCCESS;
596 }
597
598 static
599 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
600                           vlc_value_t oldval, vlc_value_t newval,
601                           void *p_data )
602 {
603     VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
604     VLC_UNUSED(oldval); VLC_UNUSED(newval);
605     intf_thread_t *p_intf = (intf_thread_t *) p_data;
606     FollowAnchor( p_intf );
607     return VLC_SUCCESS;
608 }
609
610 static
611 void GoBack( intf_thread_t *p_intf )
612 {
613     vlc_value_t history;
614     history_t *p_history = NULL;
615     history_item_t *p_history_item = NULL;
616     history_item_t *p_new_history_item = NULL;
617     playlist_t *p_playlist = NULL;
618     char *psz_timed_url = NULL;
619     playlist_item_t *p_current_item;
620
621 #ifdef CMML_INTF_DEBUG
622     msg_Dbg( p_intf, "Going back in navigation history" );
623 #endif
624
625     /* Find the playlist */
626     p_playlist = pl_Hold( p_intf );
627
628     /* Retrieve navigation history from playlist */
629     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
630         !history.p_address )
631     {
632         /* History doesn't exist yet: ignore user's request */
633         msg_Warn( p_intf, "can't go back: no history exists yet" );
634         vlc_object_release( p_playlist );
635         return;
636     }
637
638     p_history = history.p_address;
639 #ifdef CMML_INTF_DEBUG
640     msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
641     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
642              p_history->p_xarray );
643 #endif
644
645     /* Check whether we can go back in the history */
646     if( history_CanGoBack( p_history ) == false )
647     {
648         msg_Warn( p_intf, "can't go back: already at beginning of history" );
649         vlc_object_release( p_playlist );
650         return;
651     }
652
653     p_current_item = playlist_CurrentPlayingItem( p_playlist );
654
655     /* Save the currently-playing media in a new history item */
656     psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
657     p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
658     free( psz_timed_url );
659
660     if( !p_new_history_item )
661     {
662 #ifdef CMML_INTF_DEBUG
663         msg_Dbg( p_intf, "back: could not initialise new history item" );
664 #endif
665         vlc_object_release( p_playlist );
666         return;
667     }
668
669     /* Go back in the history, saving the currently-playing item */
670     (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
671     p_history_item = history_Item( p_history );
672
673 #ifdef CMML_INTF_DEBUG
674     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
675     msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
676     msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
677 #endif
678
679     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
680     vlc_object_release( p_playlist );
681 }
682
683 static
684 void GoForward( intf_thread_t *p_intf )
685 {
686     vlc_value_t history;
687     history_t *p_history = NULL;
688     history_item_t *p_history_item = NULL;
689     history_item_t *p_new_history_item = NULL;
690     playlist_t *p_playlist = NULL;
691     playlist_item_t *p_current_item;
692
693 #ifdef CMML_INTF_DEBUG
694     msg_Dbg( p_intf, "Going forward in navigation history" );
695 #endif
696
697     /* Find the playlist */
698     p_playlist = pl_Hold( p_intf );
699
700     /* Retrieve navigation history from playlist */
701     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
702         !history.p_address )
703     {
704         /* History doesn't exist yet: ignore user's request */
705         msg_Warn( p_intf, "can't go back: no history exists yet" );
706         vlc_object_release( p_playlist );
707         return;
708     }
709
710     p_history = history.p_address;
711 #ifdef CMML_INTF_DEBUG
712     msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
713     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
714              p_history->p_xarray );
715 #endif
716
717     /* Check whether we can go forward in the history */
718     if( history_CanGoForward( p_history ) == false )
719     {
720         msg_Warn( p_intf, "can't go forward: already at end of history" );
721         vlc_object_release( p_playlist );
722         return;
723     }
724
725     /* Save the currently-playing media in a new history item */
726     p_new_history_item = malloc( sizeof(history_item_t) );
727     if( !p_new_history_item )
728     {
729 #ifdef CMML_INTF_DEBUG
730         msg_Dbg( p_intf, "forward: could not initialise new history item" );
731 #endif
732         vlc_object_release( p_playlist );
733         return;
734     }
735     p_current_item = playlist_CurrentPlayingItem( p_playlist );
736     p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
737             p_current_item );
738     p_new_history_item->psz_name = p_new_history_item->psz_uri;
739
740     /* Go forward in the history, saving the currently-playing item */
741     (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
742     p_history_item = history_Item( p_history );
743
744 #ifdef CMML_INTF_DEBUG
745     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
746     msg_Dbg( p_intf, "got next history item: %p", p_history_item );
747     msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
748 #endif
749
750     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
751     vlc_object_release( p_playlist );
752 }
753
754 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
755 {
756     playlist_Stop( p_playlist );
757     (void) playlist_Add( p_playlist, psz_uri, psz_uri,
758                          PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, true /* FIXME: p_playlist->status.i_index */,
759                          false);
760 }
761
762 /****************************************************************************
763  * DisplayAnchor: displays an anchor on the given video output
764  ****************************************************************************/
765 static int DisplayAnchor( intf_thread_t *p_intf,
766         vout_thread_t *p_vout,
767         char *psz_anchor_description,
768         char *psz_anchor_url )
769 {
770     int i_margin_h, i_margin_v;
771     mtime_t i_now;
772
773     i_margin_h = 0;
774     i_margin_v = 10;
775
776     i_now = mdate();
777
778     if( p_vout )
779     {
780         if( psz_anchor_url )
781         {
782             /* Should display subtitle underlined and in blue, but it looks
783              * like VLC doesn't implement any text styles yet.  D'oh! */
784             // p_style = &blue_with_underline;
785
786         }
787
788         /* TODO: p_subpicture doesn't have the proper i_x and i_y
789          * coordinates.  Need to look at the subpicture display system to
790          * work out why. */
791         if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
792                 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
793                 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
794         {
795             /* Displayed successfully */
796         }
797         else
798         {
799             return VLC_EGENERIC;
800         }
801     }
802     else
803     {
804         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
805         return VLC_EGENERIC;
806     }
807
808     return VLC_SUCCESS;
809 }
810
811 static history_t * GetHistory( playlist_t *p_playlist )
812 {
813     vlc_value_t val;
814     history_t *p_history = NULL;
815
816     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
817     {
818         /* history doesn't exist yet: need to create it */
819         history_t *new_history = history_New();
820         val.p_address = new_history;
821         var_Create( p_playlist, "navigation-history",
822                 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
823         if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
824         {
825             msg_Warn( p_playlist, "could not initialise history" );
826         }
827         else
828         {
829             p_history = new_history;
830 #ifdef CMML_INTF_HISTORY_DEBUG
831             msg_Dbg( p_playlist, "nav history created at %p", new_history );
832             msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
833                      p_history->i_index, p_history->p_xarray );
834 #endif
835         }
836     }
837     else
838     {
839         p_history = val.p_address;
840 #ifdef CMML_INTF_HISTORY_DEBUG
841         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
842 #endif
843     }
844
845     return p_history;
846 }
847