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 *****************************************************************************/
42 #include <vlc_codec.h>
43 #include <vlc_input.h>
44 #include <vlc_interface.h>
46 #include <vlc_playlist.h>
51 #include "browser_open.h"
56 #undef CMML_INTF_USE_TIMED_URIS
58 #undef CMML_INTF_DEBUG
59 #undef CMML_INTF_HISTORY_DEBUG
61 /*****************************************************************************
62 * intf_sys_t: description and status of interface
63 *****************************************************************************/
66 decoder_t * p_cmml_decoder;
67 input_thread_t * p_input;
69 vlc_bool_t b_key_pressed;
72 struct navigation_history_t
78 /*****************************************************************************
80 *****************************************************************************/
81 static int InitThread ( intf_thread_t * );
82 static int MouseEvent ( vlc_object_t *, char const *,
83 vlc_value_t, vlc_value_t, void * );
84 static int KeyEvent ( vlc_object_t *, char const *,
85 vlc_value_t, vlc_value_t, void * );
87 static void FollowAnchor ( intf_thread_t * );
88 static void GoBack ( intf_thread_t * );
89 static void GoForward ( intf_thread_t * );
91 static int FollowAnchorCallback ( vlc_object_t *, char const *,
92 vlc_value_t, vlc_value_t, void * );
93 static int GoBackCallback ( vlc_object_t *, char const *,
94 vlc_value_t, vlc_value_t, void * );
95 static int GoForwardCallback ( vlc_object_t *, char const *,
96 vlc_value_t, vlc_value_t, void * );
98 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
99 static char *GetTimedURIFragmentForTime ( int );
100 static int GetCurrentTimeInSeconds ( input_thread_t * );
101 static int DisplayAnchor ( intf_thread_t *, vout_thread_t *,
103 static int DisplayPendingAnchor ( intf_thread_t *, vout_thread_t * );
104 static history_t * GetHistory ( playlist_t * );
105 static void ReplacePlaylistItem ( playlist_t *, char * );
107 /* Exported functions */
108 static void RunIntf ( intf_thread_t *p_intf );
110 /*****************************************************************************
111 * OpenIntf: initialize CMML interface
112 *****************************************************************************/
113 int E_(OpenIntf) ( vlc_object_t *p_this )
115 intf_thread_t *p_intf = (intf_thread_t *)p_this;
117 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
118 if( p_intf->p_sys == NULL )
123 p_intf->pf_run = RunIntf;
125 var_AddCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
126 /* we also need to add the callback for "mouse-clicked", but do that later
127 * when we've found a p_vout */
129 var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
130 var_AddCallback( p_intf->p_libvlc, "browse-go-back",
131 GoBackCallback, p_intf );
132 var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
133 var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
134 GoForwardCallback, p_intf );
135 var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
136 var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
137 FollowAnchorCallback, p_intf );
142 /*****************************************************************************
143 * CloseIntf: destroy dummy interface
144 *****************************************************************************/
145 void E_(CloseIntf) ( vlc_object_t *p_this )
147 intf_thread_t * p_intf = (intf_thread_t *)p_this;
148 vout_thread_t * p_vout;
150 #ifdef CMML_INTF_DEBUG
151 msg_Dbg( p_intf, "freeing CMML interface" );
154 /* erase the anchor text description from the video output if it exists */
155 p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
158 /* enable CMML as a subtitle track */
159 spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
160 vlc_object_release( p_vout );
163 var_DelCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
165 vlc_object_release( p_intf->p_sys->p_cmml_decoder );
167 free( p_intf->p_sys );
171 /*****************************************************************************
173 *****************************************************************************/
174 static void RunIntf( intf_thread_t *p_intf )
176 vout_thread_t * p_vout = NULL;
178 if( InitThread( p_intf ) < 0 )
180 msg_Err( p_intf, "can't initialize CMML interface" );
183 #ifdef CMML_INTF_DEBUG
184 msg_Dbg( p_intf, "CMML intf initialized" );
187 /* if video output is dying, disassociate ourselves from it */
188 if( p_vout && p_vout->b_die )
190 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
191 vlc_object_release( p_vout );
196 while( !p_intf->b_die )
199 /* find a video output if we currently don't have one */
202 p_vout = vlc_object_find( p_intf->p_sys->p_input,
203 VLC_OBJECT_VOUT, FIND_CHILD );
206 #ifdef CMML_INTF_DEBUG
207 msg_Dbg( p_intf, "found vout thread" );
209 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
213 vlc_mutex_lock( &p_intf->change_lock );
218 if( p_intf->p_sys->b_key_pressed )
221 int i, i_action = -1;
222 struct hotkey *p_hotkeys = p_intf->p_libvlc->p_hotkeys;
224 /* Find action triggered by hotkey (if any) */
225 var_Get( p_intf->p_libvlc, "key-pressed", &val );
227 /* Acknowledge that we've handled the b_key_pressed event */
228 p_intf->p_sys->b_key_pressed = VLC_FALSE;
230 #ifdef CMML_INTF_DEBUG
231 msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
234 for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
236 if( p_hotkeys[i].i_key == val.i_int )
237 i_action = p_hotkeys[i].i_action;
240 /* What did the user do? */
245 case ACTIONID_NAV_ACTIVATE:
246 FollowAnchor( p_intf );
248 case ACTIONID_HISTORY_BACK:
251 case ACTIONID_HISTORY_FORWARD:
260 vlc_mutex_unlock( &p_intf->change_lock );
262 (void) DisplayPendingAnchor( p_intf, p_vout );
265 msleep( INTF_IDLE_SLEEP );
268 /* if we're here, the video output is dying: release the vout object */
272 var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
273 vlc_object_release( p_vout );
276 vlc_object_release( p_intf->p_sys->p_input );
279 /*****************************************************************************
280 * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
281 * decoder and display it on screen
282 *****************************************************************************/
283 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
285 decoder_t *p_cmml_decoder;
286 char *psz_description = NULL;
287 char *psz_url = NULL;
289 intf_thread_t *p_primary_intf;
292 p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
293 if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
302 psz_description = val.p_address;
304 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
307 psz_url = val.p_address;
312 /* don't display anchor if main interface can display it */
313 p_primary_intf = vlc_object_find( p_intf->p_libvlc, VLC_OBJECT_INTF,
318 if( var_Get( p_primary_intf, "intf-displays-cmml-description", &val )
321 if( val.b_bool == VLC_TRUE )
323 vlc_object_release( p_primary_intf );
328 vlc_object_release( p_primary_intf );
331 /* display anchor as subtitle on-screen */
332 if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
335 /* text render unsuccessful: do nothing */
339 /* text render successful: clear description */
340 val.p_address = NULL;
341 if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
345 "reset of psz-current-anchor-description failed" );
347 free( psz_description );
355 /*****************************************************************************
357 *****************************************************************************/
358 static int InitThread( intf_thread_t * p_intf )
360 /* We might need some locking here */
363 input_thread_t * p_input;
364 decoder_t *p_cmml_decoder;
366 p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
367 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
369 #ifdef CMML_INTF_DEBUG
370 msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
371 p_cmml_decoder, p_input );
374 /* Maybe the input just died */
375 if( p_input == NULL )
380 vlc_mutex_lock( &p_intf->change_lock );
382 p_intf->p_sys->p_input = p_input;
383 p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
385 p_intf->p_sys->b_key_pressed = VLC_FALSE;
387 vlc_mutex_unlock( &p_intf->change_lock );
397 /*****************************************************************************
398 * MouseEvent: callback for mouse events
399 *****************************************************************************/
400 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
401 vlc_value_t oldval, vlc_value_t newval, void *p_data )
403 /* TODO: handle mouse clicks on the anchor text */
408 /*****************************************************************************
409 * KeyEvent: callback for keyboard events
410 *****************************************************************************/
411 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
412 vlc_value_t oldval, vlc_value_t newval, void *p_data )
414 intf_thread_t *p_intf = (intf_thread_t *)p_data;
415 vlc_mutex_lock( &p_intf->change_lock );
417 p_intf->p_sys->b_key_pressed = VLC_TRUE;
419 vlc_mutex_unlock( &p_intf->change_lock );
424 /*****************************************************************************
425 * FollowAnchor: follow the current anchor being displayed to the user
426 *****************************************************************************/
427 static void FollowAnchor ( intf_thread_t *p_intf )
430 decoder_t *p_cmml_decoder;
431 char *psz_url = NULL;
434 msg_Dbg( p_intf, "User followed anchor" );
436 p_sys = p_intf->p_sys;
437 p_cmml_decoder = p_sys->p_cmml_decoder;
439 if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
442 if( val.p_address ) psz_url = val.p_address;
445 #ifdef CMML_INTF_DEBUG
446 msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
451 playlist_t *p_playlist;
452 playlist_item_t *p_current_item;
453 char *psz_uri_to_load;
457 p_playlist = (playlist_t *) vlc_object_find( p_intf,
458 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
461 msg_Warn( p_intf, "can't find playlist" );
466 p_current_item = p_playlist->status.p_item;
467 char *psz_uri = input_item_GetURI( p_current_item->p_input );
468 #ifdef CMML_INTF_DEBUG
469 msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
472 psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
475 #ifdef CMML_INTF_DEBUG
476 msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
479 if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
481 msg_Dbg( p_intf, "couldn't get time from current clip" );
484 i_seconds = time.i_time / 1000000;
485 #ifdef CMML_INTF_DEBUG
486 msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
489 /* TODO: we need a (much) more robust way of detecting whether
490 * the file's a media file ... */
491 if( strstr( psz_uri_to_load, ".anx" ) != NULL )
493 history_t *p_history = NULL;
494 history_item_t *p_history_item = NULL;
497 p_history = GetHistory( p_playlist );
499 /* create history item */
500 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
501 p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
502 free( psz_timed_url );
504 if( !p_history_item )
506 msg_Warn( p_intf, "could not initialise history item" );
510 #ifdef CMML_INTF_DEBUG
511 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
513 history_PruneAndInsert( p_history, p_history_item );
514 #ifdef CMML_INTF_DEBUG
515 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
516 p_history_item, p_history_item->psz_uri );
517 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
521 /* free current-anchor-url */
523 val.p_address = NULL;
524 if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
527 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
530 ReplacePlaylistItem( p_playlist, psz_uri_to_load );
534 #ifdef CMML_INTF_DEBUG
535 msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
537 (void) browser_Open( psz_url );
538 playlist_Control( p_playlist, PLAYLIST_PAUSE, VLC_TRUE, 0 );
541 free( psz_uri_to_load );
543 vlc_object_release( p_playlist );
548 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
549 playlist_item_t *p_current_item )
551 #ifdef CMML_INTF_USE_TIMED_URIS
552 char *psz_url = NULL;
553 char *psz_return_value = NULL;
554 char *psz_seconds = NULL;
557 char *psz_uri = input_item_GetURI( p_current_item->p_input );
558 psz_url = XURL_GetWithoutFragment( psz_uri );
561 /* Get current time as a string */
562 if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
563 psz_url = xstrcat( psz_url, "#" );
565 psz_url = xstrcat( psz_url, "?" );
567 /* jump back to 2 seconds before where we are now */
568 i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
569 psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
572 psz_url = xstrcat( psz_url, psz_seconds );
574 psz_return_value = psz_url;
577 return psz_return_value;
581 /* Suppress warning messages about unused functions */
582 p = GetTimedURIFragmentForTime; /* unused */
583 p = GetCurrentTimeInSeconds; /* unused */
585 return input_item_GetURI( p_current_item->p_input );
591 * Get the current time, rounded down to the nearest second
593 * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
596 int GetCurrentTimeInSeconds( input_thread_t *p_input )
601 var_Get( p_input, "time", &time );
602 i_seconds = time.i_time / 1000000;
607 char *GetTimedURIFragmentForTime( int seconds )
611 asprintf( &psz_time, "%d", seconds );
616 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
617 vlc_value_t oldval, vlc_value_t newval, void *p_data )
619 intf_thread_t *p_intf = (intf_thread_t *) p_data;
625 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
626 vlc_value_t oldval, vlc_value_t newval, void *p_data )
628 intf_thread_t *p_intf = (intf_thread_t *) p_data;
634 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
635 vlc_value_t oldval, vlc_value_t newval,
638 intf_thread_t *p_intf = (intf_thread_t *) p_data;
639 FollowAnchor( p_intf );
644 void GoBack( intf_thread_t *p_intf )
647 history_t *p_history = NULL;
648 history_item_t *p_history_item = NULL;
649 history_item_t *p_new_history_item = NULL;
650 playlist_t *p_playlist = NULL;
651 char *psz_timed_url = NULL;
652 playlist_item_t *p_current_item;
654 #ifdef CMML_INTF_DEBUG
655 msg_Dbg( p_intf, "Going back in navigation history" );
658 /* Find the playlist */
659 p_playlist = (playlist_t *) vlc_object_find( p_intf,
660 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
663 msg_Warn( p_intf, "can't find playlist" );
667 /* Retrieve navigation history from playlist */
668 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
671 /* History doesn't exist yet: ignore user's request */
672 msg_Warn( p_intf, "can't go back: no history exists yet" );
673 vlc_object_release( p_playlist );
677 p_history = history.p_address;
678 #ifdef CMML_INTF_DEBUG
679 msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
680 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
681 p_history->p_xarray );
684 /* Check whether we can go back in the history */
685 if( history_CanGoBack( p_history ) == VLC_FALSE )
687 msg_Warn( p_intf, "can't go back: already at beginning of history" );
688 vlc_object_release( p_playlist );
692 p_current_item = p_playlist->status.p_item;
694 /* Save the currently-playing media in a new history item */
695 psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
696 p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
697 free( psz_timed_url );
699 if( !p_new_history_item )
701 #ifdef CMML_INTF_DEBUG
702 msg_Dbg( p_intf, "back: could not initialise new history item" );
704 vlc_object_release( p_playlist );
708 /* Go back in the history, saving the currently-playing item */
709 (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
710 p_history_item = history_Item( p_history );
712 #ifdef CMML_INTF_DEBUG
713 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
714 msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
715 msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
718 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
719 vlc_object_release( p_playlist );
723 void GoForward( intf_thread_t *p_intf )
726 history_t *p_history = NULL;
727 history_item_t *p_history_item = NULL;
728 history_item_t *p_new_history_item = NULL;
729 playlist_t *p_playlist = NULL;
730 playlist_item_t *p_current_item;
732 #ifdef CMML_INTF_DEBUG
733 msg_Dbg( p_intf, "Going forward in navigation history" );
736 /* Find the playlist */
737 p_playlist = (playlist_t *) vlc_object_find( p_intf,
738 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
741 msg_Warn( p_intf, "can't find playlist" );
745 /* Retrieve navigation history from playlist */
746 if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
749 /* History doesn't exist yet: ignore user's request */
750 msg_Warn( p_intf, "can't go back: no history exists yet" );
751 vlc_object_release( p_playlist );
755 p_history = history.p_address;
756 #ifdef CMML_INTF_DEBUG
757 msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
758 msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
759 p_history->p_xarray );
762 /* Check whether we can go forward in the history */
763 if( history_CanGoForward( p_history ) == VLC_FALSE )
765 msg_Warn( p_intf, "can't go forward: already at end of history" );
766 vlc_object_release( p_playlist );
770 /* Save the currently-playing media in a new history item */
771 p_new_history_item = malloc( sizeof(history_item_t) );
772 if( !p_new_history_item )
774 #ifdef CMML_INTF_DEBUG
775 msg_Dbg( p_intf, "forward: could not initialise new history item" );
777 vlc_object_release( p_playlist );
780 p_current_item = p_playlist->status.p_item;
781 p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
783 p_new_history_item->psz_name = p_new_history_item->psz_uri;
785 /* Go forward in the history, saving the currently-playing item */
786 (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
787 p_history_item = history_Item( p_history );
789 #ifdef CMML_INTF_DEBUG
790 msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
791 msg_Dbg( p_intf, "got next history item: %p", p_history_item );
792 msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
795 ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
796 vlc_object_release( p_playlist );
799 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
801 playlist_Stop( p_playlist );
802 (void) playlist_Add( p_playlist, psz_uri, psz_uri,
803 PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, VLC_TRUE /* FIXME: p_playlist->status.i_index */,
807 /****************************************************************************
808 * DisplayAnchor: displays an anchor on the given video output
809 ****************************************************************************/
810 static int DisplayAnchor( intf_thread_t *p_intf,
811 vout_thread_t *p_vout,
812 char *psz_anchor_description,
813 char *psz_anchor_url )
815 int i_margin_h, i_margin_v;
827 /* Should display subtitle underlined and in blue, but it looks
828 * like VLC doesn't implement any text styles yet. D'oh! */
829 // p_style = &blue_with_underline;
833 /* TODO: p_subpicture doesn't have the proper i_x and i_y
834 * coordinates. Need to look at the subpicture display system to
836 if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
837 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
838 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
840 /* Displayed successfully */
849 msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
856 static history_t * GetHistory( playlist_t *p_playlist )
859 history_t *p_history = NULL;
861 if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
863 /* history doesn't exist yet: need to create it */
864 history_t *new_history = history_New();
865 val.p_address = new_history;
866 var_Create( p_playlist, "navigation-history",
867 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
868 if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
870 msg_Warn( p_playlist, "could not initialise history" );
874 p_history = new_history;
875 #ifdef CMML_INTF_HISTORY_DEBUG
876 msg_Dbg( p_playlist, "nav history created at %p", new_history );
877 msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
878 p_history->i_index, p_history->p_xarray );
884 p_history = val.p_address;
885 #ifdef CMML_INTF_HISTORY_DEBUG
886 msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );