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
10 * Authors: Andre Pang <Andre.Pang@csiro.au>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
38 #include <vlc_codec.h>
39 #include <vlc_input.h>
40 #include <vlc_interface.h>
42 #include <vlc_playlist.h>
47 #include "browser_open.h"
52 #undef CMML_INTF_USE_TIMED_URIS
54 #undef CMML_INTF_DEBUG
55 #undef CMML_INTF_HISTORY_DEBUG
57 /*****************************************************************************
58 * intf_sys_t: description and status of interface
59 *****************************************************************************/
62 decoder_t * p_cmml_decoder;
63 input_thread_t * p_input;
65 vlc_bool_t b_key_pressed;
68 struct navigation_history_t
74 /*****************************************************************************
76 *****************************************************************************/
77 static int InitThread ( intf_thread_t * );
78 static int MouseEvent ( vlc_object_t *, char const *,
79 vlc_value_t, vlc_value_t, void * );
80 static int KeyEvent ( vlc_object_t *, char const *,
81 vlc_value_t, vlc_value_t, void * );
83 static void FollowAnchor ( intf_thread_t * );
84 static void GoBack ( intf_thread_t * );
85 static void GoForward ( intf_thread_t * );
87 static int FollowAnchorCallback ( vlc_object_t *, char const *,
88 vlc_value_t, vlc_value_t, void * );
89 static int GoBackCallback ( vlc_object_t *, char const *,
90 vlc_value_t, vlc_value_t, void * );
91 static int GoForwardCallback ( vlc_object_t *, char const *,
92 vlc_value_t, vlc_value_t, void * );
94 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
95 static char *GetTimedURIFragmentForTime ( int );
96 static int GetCurrentTimeInSeconds ( input_thread_t * );
97 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
99 static int DisplayPendingAnchor ( intf_thread_t *, vout_thread_t * );
100 static history_t * GetHistory ( playlist_t * );
101 static void ReplacePlaylistItem ( playlist_t *, char * );
103 /* Exported functions */
104 static void RunIntf ( intf_thread_t *p_intf );
106 /*****************************************************************************
107 * OpenIntf: initialize CMML interface
108 *****************************************************************************/
109 int E_(OpenIntf) ( vlc_object_t *p_this )
111 intf_thread_t *p_intf = (intf_thread_t *)p_this;
113 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
114 if( p_intf->p_sys == NULL )
119 p_intf->pf_run = RunIntf;
121 var_AddCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
122 /* we also need to add the callback for "mouse-clicked", but do that later
123 * when we've found a p_vout */
125 var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
126 var_AddCallback( p_intf->p_libvlc, "browse-go-back",
127 GoBackCallback, p_intf );
128 var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
129 var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
130 GoForwardCallback, p_intf );
131 var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
132 var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
133 FollowAnchorCallback, p_intf );
138 /*****************************************************************************
139 * CloseIntf: destroy dummy interface
140 *****************************************************************************/
141 void E_(CloseIntf) ( vlc_object_t *p_this )
143 intf_thread_t * p_intf = (intf_thread_t *)p_this;
144 vout_thread_t * p_vout;
146 #ifdef CMML_INTF_DEBUG
147 msg_Dbg( p_intf, "freeing CMML interface" );
150 /* erase the anchor text description from the video output if it exists */
151 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
154 /* enable CMML as a subtitle track */
155 spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
156 vlc_object_release( p_vout );
159 var_DelCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
161 vlc_object_release( p_intf->p_sys->p_cmml_decoder );
163 free( p_intf->p_sys );
167 /*****************************************************************************
169 *****************************************************************************/
170 static void RunIntf( intf_thread_t *p_intf )
172 vout_thread_t * p_vout = NULL;
174 if( InitThread( p_intf ) < 0 )
176 msg_Err( p_intf, "can't initialize CMML interface" );
179 #ifdef CMML_INTF_DEBUG
180 msg_Dbg( p_intf, "CMML intf initialized" );
183 /* if video output is dying, disassociate ourselves from it */
184 if( p_vout && p_vout->b_die )
186 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
187 vlc_object_release( p_vout );
192 while( !p_intf->b_die )
195 /* find a video output if we currently don't have one */
198 p_vout = vlc_object_find( p_intf->p_sys->p_input,
199 VLC_OBJECT_VOUT, FIND_CHILD );
202 #ifdef CMML_INTF_DEBUG
203 msg_Dbg( p_intf, "found vout thread" );
205 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
209 vlc_mutex_lock( &p_intf->change_lock );
214 if( p_intf->p_sys->b_key_pressed )
217 int i, i_action = -1;
218 struct hotkey *p_hotkeys = p_intf->p_libvlc->p_hotkeys;
220 /* Find action triggered by hotkey (if any) */
221 var_Get( p_intf->p_libvlc, "key-pressed", &val );
223 /* Acknowledge that we've handled the b_key_pressed event */
224 p_intf->p_sys->b_key_pressed = VLC_FALSE;
226 #ifdef CMML_INTF_DEBUG
227 msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
230 for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
232 if( p_hotkeys[i].i_key == val.i_int )
233 i_action = p_hotkeys[i].i_action;
236 /* What did the user do? */
241 case ACTIONID_NAV_ACTIVATE:
242 FollowAnchor( p_intf );
244 case ACTIONID_HISTORY_BACK:
247 case ACTIONID_HISTORY_FORWARD:
256 vlc_mutex_unlock( &p_intf->change_lock );
258 (void) DisplayPendingAnchor( p_intf, p_vout );
261 msleep( INTF_IDLE_SLEEP );
264 /* if we're here, the video output is dying: release the vout object */
268 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
269 vlc_object_release( p_vout );
272 vlc_object_release( p_intf->p_sys->p_input );
275 /*****************************************************************************
276 * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
277 * decoder and display it on screen
278 *****************************************************************************/
279 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
281 decoder_t *p_cmml_decoder;
282 char *psz_description = NULL;
283 char *psz_url = NULL;
285 intf_thread_t *p_primary_intf;
288 p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
289 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
298 psz_description = val.p_address;
300 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
303 psz_url = val.p_address;
308 /* don't display anchor if main interface can display it */
309 p_primary_intf = vlc_object_find( p_intf->p_libvlc, VLC_OBJECT_INTF,
314 if( var_Get( p_primary_intf, "intf-displays-cmml-description", &val )
317 if( val.b_bool == VLC_TRUE )
319 vlc_object_release( p_primary_intf );
324 vlc_object_release( p_primary_intf );
327 /* display anchor as subtitle on-screen */
328 if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
331 /* text render unsuccessful: do nothing */
335 /* text render successful: clear description */
336 val.p_address = NULL;
337 if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
341 "reset of psz-current-anchor-description failed" );
343 free( psz_description );
351 /*****************************************************************************
353 *****************************************************************************/
354 static int InitThread( intf_thread_t * p_intf )
356 /* We might need some locking here */
359 input_thread_t * p_input;
360 decoder_t *p_cmml_decoder;
362 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
363 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
365 #ifdef CMML_INTF_DEBUG
366 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
367 p_cmml_decoder, p_input );
370 /* Maybe the input just died */
371 if( p_input == NULL )
376 vlc_mutex_lock( &p_intf->change_lock );
378 p_intf->p_sys->p_input = p_input;
379 p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
381 p_intf->p_sys->b_key_pressed = VLC_FALSE;
383 vlc_mutex_unlock( &p_intf->change_lock );
393 /*****************************************************************************
394 * MouseEvent: callback for mouse events
395 *****************************************************************************/
396 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
397 vlc_value_t oldval, vlc_value_t newval, void *p_data )
399 /* TODO: handle mouse clicks on the anchor text */
404 /*****************************************************************************
405 * KeyEvent: callback for keyboard events
406 *****************************************************************************/
407 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
408 vlc_value_t oldval, vlc_value_t newval, void *p_data )
410 intf_thread_t *p_intf = (intf_thread_t *)p_data;
411 vlc_mutex_lock( &p_intf->change_lock );
413 p_intf->p_sys->b_key_pressed = VLC_TRUE;
415 vlc_mutex_unlock( &p_intf->change_lock );
420 /*****************************************************************************
421 * FollowAnchor: follow the current anchor being displayed to the user
422 *****************************************************************************/
423 static void FollowAnchor ( intf_thread_t *p_intf )
426 decoder_t *p_cmml_decoder;
427 char *psz_url = NULL;
430 msg_Dbg( p_intf, "User followed anchor" );
432 p_sys = p_intf->p_sys;
433 p_cmml_decoder = p_sys->p_cmml_decoder;
435 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
438 if( val.p_address ) psz_url = val.p_address;
441 #ifdef CMML_INTF_DEBUG
442 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
447 playlist_t *p_playlist;
448 playlist_item_t *p_current_item;
449 char *psz_uri_to_load;
453 p_playlist = (playlist_t *) vlc_object_find( p_intf,
454 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
457 msg_Warn( p_intf, "can't find playlist" );
462 p_current_item = p_playlist->status.p_item;
463 #ifdef CMML_INTF_DEBUG
464 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"",
465 p_current_item->input.psz_uri );
468 psz_uri_to_load = XURL_Concat( p_current_item->p_input->psz_uri,
471 #ifdef CMML_INTF_DEBUG
472 msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
475 if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
477 msg_Dbg( p_intf, "couldn't get time from current clip" );
480 i_seconds = time.i_time / 1000000;
481 #ifdef CMML_INTF_DEBUG
482 msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
485 /* TODO: we need a (much) more robust way of detecting whether
486 * the file's a media file ... */
487 if( strstr( psz_uri_to_load, ".anx" ) != NULL )
489 history_t *p_history = NULL;
490 history_item_t *p_history_item = NULL;
493 p_history = GetHistory( p_playlist );
495 /* create history item */
496 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
497 p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
498 free( psz_timed_url );
500 if( !p_history_item )
502 msg_Warn( p_intf, "could not initialise history item" );
506 #ifdef CMML_INTF_DEBUG
507 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
509 history_PruneAndInsert( p_history, p_history_item );
510 #ifdef CMML_INTF_DEBUG
511 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
512 p_history_item, p_history_item->psz_uri );
513 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
517 /* free current-anchor-url */
519 val.p_address = NULL;
520 if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
523 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
526 ReplacePlaylistItem( p_playlist, psz_uri_to_load );
530 #ifdef CMML_INTF_DEBUG
531 msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
533 (void) browser_Open( psz_url );
534 playlist_Control( p_playlist, PLAYLIST_PAUSE, VLC_TRUE, 0 );
537 free( psz_uri_to_load );
539 vlc_object_release( p_playlist );
544 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
545 playlist_item_t *p_current_item )
547 #ifdef CMML_INTF_USE_TIMED_URIS
548 char *psz_url = NULL;
549 char *psz_return_value = NULL;
550 char *psz_seconds = NULL;
553 psz_url = XURL_GetWithoutFragment( p_current_item->input->psz_uri );
555 /* Get current time as a string */
556 if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
557 psz_url = xstrcat( psz_url, "#" );
559 psz_url = xstrcat( psz_url, "?" );
561 /* jump back to 2 seconds before where we are now */
562 i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
563 psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
566 psz_url = xstrcat( psz_url, psz_seconds );
568 psz_return_value = psz_url;
571 return psz_return_value;
575 /* Suppress warning messages about unused functions */
576 p = GetTimedURIFragmentForTime; /* unused */
577 p = GetCurrentTimeInSeconds; /* unused */
579 return strdup( p_current_item->p_input->psz_uri );
585 * Get the current time, rounded down to the nearest second
587 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
590 int GetCurrentTimeInSeconds( input_thread_t *p_input )
595 var_Get( p_input, "time", &time );
596 i_seconds = time.i_time / 1000000;
601 char *GetTimedURIFragmentForTime( int seconds )
605 asprintf( &psz_time, "%d", seconds );
610 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
611 vlc_value_t oldval, vlc_value_t newval, void *p_data )
613 intf_thread_t *p_intf = (intf_thread_t *) p_data;
619 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
620 vlc_value_t oldval, vlc_value_t newval, void *p_data )
622 intf_thread_t *p_intf = (intf_thread_t *) p_data;
628 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
629 vlc_value_t oldval, vlc_value_t newval,
632 intf_thread_t *p_intf = (intf_thread_t *) p_data;
633 FollowAnchor( p_intf );
638 void GoBack( intf_thread_t *p_intf )
641 history_t *p_history = NULL;
642 history_item_t *p_history_item = NULL;
643 history_item_t *p_new_history_item = NULL;
644 playlist_t *p_playlist = NULL;
645 char *psz_timed_url = NULL;
646 playlist_item_t *p_current_item;
648 #ifdef CMML_INTF_DEBUG
649 msg_Dbg( p_intf, "Going back in navigation history" );
652 /* Find the playlist */
653 p_playlist = (playlist_t *) vlc_object_find( p_intf,
654 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
657 msg_Warn( p_intf, "can't find playlist" );
661 /* Retrieve navigation history from playlist */
662 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
665 /* History doesn't exist yet: ignore user's request */
666 msg_Warn( p_intf, "can't go back: no history exists yet" );
667 vlc_object_release( p_playlist );
671 p_history = history.p_address;
672 #ifdef CMML_INTF_DEBUG
673 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
674 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
675 p_history->p_xarray );
678 /* Check whether we can go back in the history */
679 if( history_CanGoBack( p_history ) == VLC_FALSE )
681 msg_Warn( p_intf, "can't go back: already at beginning of history" );
682 vlc_object_release( p_playlist );
686 p_current_item = p_playlist->status.p_item;
688 /* Save the currently-playing media in a new history item */
689 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
690 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
691 free( psz_timed_url );
693 if( !p_new_history_item )
695 #ifdef CMML_INTF_DEBUG
696 msg_Dbg( p_intf, "back: could not initialise new history item" );
698 vlc_object_release( p_playlist );
702 /* Go back in the history, saving the currently-playing item */
703 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
704 p_history_item = history_Item( p_history );
706 #ifdef CMML_INTF_DEBUG
707 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
708 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
709 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
712 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
713 vlc_object_release( p_playlist );
717 void GoForward( intf_thread_t *p_intf )
720 history_t *p_history = NULL;
721 history_item_t *p_history_item = NULL;
722 history_item_t *p_new_history_item = NULL;
723 playlist_t *p_playlist = NULL;
724 playlist_item_t *p_current_item;
726 #ifdef CMML_INTF_DEBUG
727 msg_Dbg( p_intf, "Going forward in navigation history" );
730 /* Find the playlist */
731 p_playlist = (playlist_t *) vlc_object_find( p_intf,
732 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
735 msg_Warn( p_intf, "can't find playlist" );
739 /* Retrieve navigation history from playlist */
740 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
743 /* History doesn't exist yet: ignore user's request */
744 msg_Warn( p_intf, "can't go back: no history exists yet" );
745 vlc_object_release( p_playlist );
749 p_history = history.p_address;
750 #ifdef CMML_INTF_DEBUG
751 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
752 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
753 p_history->p_xarray );
756 /* Check whether we can go forward in the history */
757 if( history_CanGoForward( p_history ) == VLC_FALSE )
759 msg_Warn( p_intf, "can't go forward: already at end of history" );
760 vlc_object_release( p_playlist );
764 /* Save the currently-playing media in a new history item */
765 p_new_history_item = malloc( sizeof(history_item_t) );
766 if( !p_new_history_item )
768 #ifdef CMML_INTF_DEBUG
769 msg_Dbg( p_intf, "forward: could not initialise new history item" );
771 vlc_object_release( p_playlist );
774 p_current_item = p_playlist->status.p_item;
775 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
777 p_new_history_item->psz_name = p_new_history_item->psz_uri;
779 /* Go forward in the history, saving the currently-playing item */
780 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
781 p_history_item = history_Item( p_history );
783 #ifdef CMML_INTF_DEBUG
784 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
785 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
786 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
789 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
790 vlc_object_release( p_playlist );
793 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
795 playlist_Stop( p_playlist );
796 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
797 PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, VLC_TRUE /* FIXME: p_playlist->status.i_index */,
801 /****************************************************************************
802 * DisplayAnchor: displays an anchor on the given video output
803 ****************************************************************************/
804 static int DisplayAnchor( intf_thread_t *p_intf,
805 vout_thread_t *p_vout,
806 char *psz_anchor_description,
807 char *psz_anchor_url )
809 int i_margin_h, i_margin_v;
821 /* Should display subtitle underlined and in blue, but it looks
822 * like VLC doesn't implement any text styles yet. D'oh! */
823 // p_style = &blue_with_underline;
827 /* TODO: p_subpicture doesn't have the proper i_x and i_y
828 * coordinates. Need to look at the subpicture display system to
830 if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
831 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
832 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
834 /* Displayed successfully */
843 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
850 static history_t * GetHistory( playlist_t *p_playlist )
853 history_t *p_history = NULL;
855 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
857 /* history doesn't exist yet: need to create it */
858 history_t *new_history = history_New();
859 val.p_address = new_history;
860 var_Create( p_playlist, "navigation-history",
861 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
862 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
864 msg_Warn( p_playlist, "could not initialise history" );
868 p_history = new_history;
869 #ifdef CMML_INTF_HISTORY_DEBUG
870 msg_Dbg( p_playlist, "nav history created at %p", new_history );
871 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
872 p_history->i_index, p_history->p_xarray );
878 p_history = val.p_address;
879 #ifdef CMML_INTF_HISTORY_DEBUG
880 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );