* all: added a new access_filter module: record + one hotkey to start/stop
[vlc.git] / modules / access_filter / timeshift.c
1 /*****************************************************************************
2  * timeshift.c
3  *****************************************************************************
4  * Copyright (C) 2005 VideoLAN
5  * $Id: demux.c 7546 2004-04-29 13:53:29Z gbazin $
6  *
7  * Author: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28
29 #include <errno.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/input.h>
33 #include <unistd.h>
34
35 /*****************************************************************************
36  * Module descriptor
37  *****************************************************************************/
38 static int  Open ( vlc_object_t * );
39 static void Close( vlc_object_t * );
40
41 vlc_module_begin();
42     set_description( _("Timeshift") );
43     set_category( CAT_INPUT );
44     set_subcategory( SUBCAT_INPUT_ACCESS_FILTER );
45     set_capability( "access_filter", 0 );
46     add_shortcut( "timeshift" );
47     set_callbacks( Open, Close );
48 vlc_module_end();
49
50 /*****************************************************************************
51  * Local prototypes
52  *****************************************************************************/
53
54 static block_t *Block  ( access_t *p_access );
55 static int      Control( access_t *, int i_query, va_list args );
56 static void     Thread ( access_t * );
57
58 #define TIMESHIFT_FIFO_MAX (4*1024*1024)
59 #define TIMESHIFT_FIFO_MIN (TIMESHIFT_FIFO_MAX/4)
60
61 struct access_sys_t
62 {
63     block_fifo_t *p_fifo;
64
65     vlc_bool_t b_opened;
66
67     FILE *t1, *t2;
68
69     char *psz_tmp1;
70     char *psz_tmp2;
71
72     FILE *w;
73     int i_w;
74
75     FILE *r;
76 };
77
78 /*****************************************************************************
79  * Open:
80  *****************************************************************************/
81 static int Open( vlc_object_t *p_this )
82 {
83     access_t *p_access = (access_t*)p_this;
84     access_t *p_src = p_access->p_source;
85     access_sys_t *p_sys;
86     vlc_bool_t b_bool;
87 #ifdef WIN32
88     char buffer[4096];
89     int i_size;
90 #endif
91
92     /* Only work with not pace controled access */
93     if( access2_Control( p_src, ACCESS_CAN_CONTROL_PACE, &b_bool ) || b_bool )
94     {
95         msg_Dbg( p_src, "ACCESS_CAN_CONTROL_PACE" );
96         return VLC_EGENERIC;
97     }
98     /* Refuse access that can be paused */
99     if( access2_Control( p_src, ACCESS_CAN_PAUSE, &b_bool ) || b_bool )
100     {
101         msg_Dbg( p_src, "ACCESS_CAN_PAUSE: timeshift useless" );
102         return VLC_EGENERIC;
103     }
104
105     /* */
106     p_access->pf_read = NULL;
107     p_access->pf_block = Block;
108     p_access->pf_seek = NULL;
109     p_access->pf_control = Control;
110
111     p_access->info = p_src->info;
112
113     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
114
115     /* */
116     p_sys->p_fifo = block_FifoNew( p_access );
117     p_sys->b_opened = VLC_FALSE;
118     p_sys->t1 = p_sys->t2 = NULL;
119     p_sys->w = p_sys->r = NULL;
120     p_sys->i_w = 0;
121
122 #ifdef WIN32
123     i_size = GetTempPath( 4095, buffer );
124     if( i_size <= 0 || i_size >= 4095 )
125     {
126         if( getcwd( buffer, 4095 ) == NULL )
127             strcpy( buffer, "c:" );
128     }
129     /* remove last \\ if any */
130     if( buffer[strlen(buffer)-1] == '\\' )
131         buffer[strlen(buffer)-1] = '\0';
132
133     asprintf( &p_sys->psz_tmp1, "%s\\vlc-timeshift-%d-%d-1.dat",
134               buffer, GetCurrentProcessId(), p_access->i_object_id );
135     asprintf( &p_sys->psz_tmp2, "%s\\vlc-timeshift-%d-%d-2.dat",
136               buffer, GetCurrentProcessId(), p_access->i_object_id );
137 #else
138     asprintf( &p_sys->psz_tmp1, "/tmp/vlc-timeshift-%d-%d-1.dat",
139               getpid(), p_access->i_object_id );
140     asprintf( &p_sys->psz_tmp2, "/tmp/vlc-timeshift-%d-%d-2.dat",
141               getpid(), p_access->i_object_id );
142 #endif
143
144     if( vlc_thread_create( p_access, "timeshift thread", Thread,
145                            VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
146     {
147         msg_Err( p_access, "cannot spawn timeshift access thread" );
148         return VLC_EGENERIC;
149     }
150
151     return VLC_SUCCESS;
152 }
153
154 /*****************************************************************************
155  * Close:
156  *****************************************************************************/
157 static void Close( vlc_object_t *p_this )
158 {
159     access_t     *p_access = (access_t*)p_this;
160     access_sys_t *p_sys = p_access->p_sys;
161
162     /* */
163     msg_Dbg( p_access, "timeshift close called" );
164     vlc_thread_join( p_access );
165
166     if( p_sys->b_opened )
167     {
168         if( p_sys->t1 ) fclose( p_sys->t1 );
169         if( p_sys->t2 ) fclose( p_sys->t2 );
170         unlink( p_sys->psz_tmp1 );
171         unlink( p_sys->psz_tmp2 );
172     }
173
174     free( p_sys->psz_tmp1 );
175     free( p_sys->psz_tmp2 );
176
177     block_FifoRelease( p_sys->p_fifo );
178     free( p_sys );
179 }
180
181 /*****************************************************************************
182  *
183  *****************************************************************************/
184 static block_t *Block( access_t *p_access )
185 {
186     access_sys_t *p_sys = p_access->p_sys;
187
188     if( p_access->b_die )
189     {
190         p_access->info.b_eof = VLC_TRUE;
191         return NULL;
192     }
193
194     return block_FifoGet( p_sys->p_fifo );
195 }
196
197 /*****************************************************************************
198  *
199  *****************************************************************************/
200 static int Control( access_t *p_access, int i_query, va_list args )
201 {
202     access_t     *p_src = p_access->p_source;
203
204     vlc_bool_t   *pb_bool;
205     int          *pi_int;
206     int64_t      *pi_64;
207
208     switch( i_query )
209     {
210         /* */
211         case ACCESS_CAN_SEEK:
212         case ACCESS_CAN_FASTSEEK:
213             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
214             *pb_bool = VLC_FALSE;
215             break;
216
217         case ACCESS_CAN_CONTROL_PACE:   /* Not really true */
218         case ACCESS_CAN_PAUSE:
219             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
220             *pb_bool = VLC_TRUE;
221             break;
222
223         /* */
224         case ACCESS_GET_MTU:
225             pi_int = (int*)va_arg( args, int * );
226             *pi_int = 0;
227             break;
228
229         case ACCESS_GET_PTS_DELAY:
230             pi_64 = (int64_t*)va_arg( args, int64_t * );
231             return access2_Control( p_src, ACCESS_GET_PTS_DELAY, pi_64 );
232         /* */
233         case ACCESS_SET_PAUSE_STATE:
234             return VLC_SUCCESS;
235
236         case ACCESS_GET_TITLE_INFO:
237         case ACCESS_SET_TITLE:
238         case ACCESS_SET_SEEKPOINT:
239         case ACCESS_GET_META:
240             return VLC_EGENERIC;
241
242         case ACCESS_SET_PRIVATE_ID_STATE:
243         case ACCESS_GET_PRIVATE_ID_STATE:
244         case ACCESS_SET_PRIVATE_ID_CA:
245             return access2_vaControl( p_src, i_query, args );
246
247         default:
248             msg_Warn( p_access, "unimplemented query in control" );
249             return VLC_EGENERIC;
250
251     }
252     return VLC_SUCCESS;
253 }
254
255 /*****************************************************************************
256  *
257  *****************************************************************************/
258 static void Thread( access_t *p_access )
259 {
260     access_sys_t *p_sys = p_access->p_sys;
261     access_t     *p_src = p_access->p_source;
262     int i_loop = 0;
263
264     while( !p_access->b_die )
265     {
266         block_t *p_block;
267
268         /* Get a new block */
269         if( p_src->pf_block )
270         {
271             p_block = p_src->pf_block( p_src );
272
273             if( p_block == NULL )
274             {
275                 if( p_src->info.b_eof )
276                     break;
277
278                 msleep( 1000 );
279                 continue;
280             }
281         }
282         else
283         {
284             if( ( p_block = block_New( p_access, 2048 ) ) == NULL )
285                 break;
286
287             p_block->i_buffer = p_src->pf_read( p_src, p_block->p_buffer, 2048);
288             if( p_block->i_buffer < 0 )
289             {
290                 block_Release( p_block );
291                 if( p_block->i_buffer == 0 )
292                     break;
293                 msleep( 1000 );
294                 continue;
295             }
296         }
297
298         /* Open dump files if we need them */
299         if( p_sys->p_fifo->i_size >= TIMESHIFT_FIFO_MAX && !p_sys->b_opened )
300         {
301             msg_Dbg( p_access, "opening first temporary files (%s)",
302                      p_sys->psz_tmp1 );
303
304             p_sys->b_opened = VLC_TRUE;
305             p_sys->t1 = p_sys->t2 = NULL;
306             p_sys->w = p_sys->r = NULL;
307
308             p_sys->t1 = fopen( p_sys->psz_tmp1, "w+b" );
309             if( p_sys->t1 )
310             {
311                 msg_Dbg( p_access, "opening second temporary files (%s)",
312                          p_sys->psz_tmp2 );
313
314                 p_sys->t2 = fopen( p_sys->psz_tmp2, "w+b" );
315                 if( p_sys->t2 )
316                 {
317                     p_sys->w = p_sys->t1;
318                     p_sys->i_w = 0;
319
320                     msg_Dbg( p_access, "start writing into temporary file" );
321                 }
322                 else
323                 {
324                     msg_Err( p_access, "cannot open temporary file '%s' (%s)",
325                              p_sys->psz_tmp2, strerror(errno) );
326                     fclose( p_sys->t1 );
327                     p_sys->t1 = NULL;
328                 }
329             }
330             else
331             {
332                 msg_Err( p_access, "cannot open temporary file '%s' (%s)",
333                          p_sys->psz_tmp1, strerror(errno) );
334             }
335         }
336
337         if( p_sys->w )
338         {
339             int i_write;
340
341             /* Dump the block */
342             i_write = fwrite( p_block->p_buffer, 1, p_block->i_buffer,
343                               p_sys->w );
344             block_Release( p_block );
345
346             if( i_write > 0 )
347                 p_sys->i_w += i_write;
348             else
349                 msg_Warn( p_access, "cannot write data" );
350
351             /* Start reading from a file if fifo isn't at 25% */
352             if( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN && !p_sys->r )
353             {
354                 msg_Dbg( p_access, "start reading from temporary file (dumped=%d)", p_sys->i_w );
355
356                 p_sys->r = p_sys->w;
357                 fseek( p_sys->r, 0, SEEK_SET );
358
359                 p_sys->w = p_sys->t2;
360                 p_sys->i_w = 0;
361             }
362
363             if( p_sys->r )
364             {
365                 while( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN )
366                 {
367                     p_block = block_New( p_access, 4096 );
368                     p_block->i_buffer = fread( p_block->p_buffer, 1, 4096,
369                                                p_sys->r );
370
371                     if( p_block->i_buffer > 0 )
372                     {
373                         block_FifoPut( p_sys->p_fifo, p_block );
374                     }
375                     else if( p_sys->i_w > 32*1024)
376                     {
377                         FILE *tmp;
378                         block_Release( p_block );
379
380                         msg_Dbg( p_access, "switching temporary files (dumped=%d)", p_sys->i_w );
381
382                         /* Switch read/write */
383                         tmp = p_sys->r;
384
385                         p_sys->r = p_sys->w;
386                         fseek( p_sys->r, 0, SEEK_SET );
387
388                         p_sys->w = tmp;
389                         fseek( p_sys->w, 0, SEEK_SET );
390                         ftruncate( fileno(p_sys->w), 0 );
391                         p_sys->i_w = 0;
392                     }
393                     else
394                     {
395                         msg_Dbg( p_access, "removing temporary files" );
396
397                         /* We will remove the need of tmp files */
398                         if( p_sys->i_w > 0 )
399                         {
400                             msg_Dbg( p_access, "loading temporary file" );
401                             fseek( p_sys->w, 0, SEEK_SET );
402                             for( ;; )
403                             {
404                                 p_block = block_New( p_access, 4096 );
405                                 p_block->i_buffer = fread( p_block->p_buffer,
406                                                            1, 4096,
407                                                            p_sys->w );
408                                 if( p_block->i_buffer <= 0 )
409                                 {
410                                     block_Release( p_block );
411                                     break;
412                                 }
413                                 block_FifoPut( p_sys->p_fifo, p_block );
414                             }
415                         }
416
417                         p_sys->b_opened = VLC_FALSE;
418
419                         fclose( p_sys->t1 );
420                         fclose( p_sys->t2 );
421
422                         p_sys->t1 = p_sys->t2 = NULL;
423                         p_sys->w = p_sys->r = NULL;
424
425                         unlink( p_sys->psz_tmp1 );
426                         unlink( p_sys->psz_tmp2 );
427                         break;
428                     }
429                 }
430             }
431         }
432         else if( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
433         {
434             block_FifoPut( p_sys->p_fifo, p_block );
435         }
436         else
437         {
438             /* We failed to opened files so trash new data */
439             block_Release( p_block );
440         }
441 #if 0
442         if( (i_loop % 400) == 0 )
443             msg_Dbg( p_access, "timeshift: buff=%d", p_sys->p_fifo->i_size );
444 #endif
445         i_loop++;
446     }
447
448     msg_Warn( p_access, "timeshift: EOF" );
449
450     /* Send dummy packet to avoid deadlock in TShiftBlock */
451     for( i_loop = 0; i_loop < 2; i_loop++ )
452     {
453         block_t *p_dummy = block_New( p_access, 128 );
454
455         p_dummy->i_flags |= BLOCK_FLAG_DISCONTINUITY;
456         memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
457
458         block_FifoPut( p_sys->p_fifo, p_dummy );
459     }
460 }
461