mmsh.c 28.6 KB
Newer Older
1 2 3
/*****************************************************************************
 * mmsh.c:
 *****************************************************************************
4
 * Copyright (C) 2001, 2002 the VideoLAN team
5
 * $Id$
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

#include <vlc/vlc.h>
Clément Stenac's avatar
Clément Stenac committed
29
#include <vlc_access.h>
30
#include "vlc_playlist.h"
31
#include "vlc_strings.h"
32

Clément Stenac's avatar
Clément Stenac committed
33
#include <vlc_network.h>
34
#include "vlc_url.h"
35 36 37 38 39 40
#include "asf.h"
#include "buffer.h"

#include "mms.h"
#include "mmsh.h"

41 42 43 44
/* TODO:
 *  - authentication
 */

45 46 47
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
48 49
int  E_(MMSHOpen)  ( access_t * );
void E_(MMSHClose) ( access_t * );
Laurent Aimar's avatar
Laurent Aimar committed
50

51 52 53 54
static int  Read( access_t *, uint8_t *, int );
static int  ReadRedirect( access_t *, uint8_t *, int );
static int  Seek( access_t *, int64_t );
static int  Control( access_t *, int, va_list );
Laurent Aimar's avatar
Laurent Aimar committed
55

56 57 58
static int  Describe( access_t  *, char **ppsz_location );
static int  Start( access_t *, int64_t );
static void Stop( access_t * );
59

60
static int  GetPacket( access_t *, chunk_t * );
61 62 63 64 65 66 67
static void GetHeader( access_t *p_access );

static int Restart( access_t * );
static int Reset( access_t * );

//#define MMSH_USER_AGENT "NSPlayer/4.1.0.3856"
#define MMSH_USER_AGENT "NSPlayer/7.10.0.3059"
68 69 70 71

/****************************************************************************
 * Open: connect to ftp server and ask for file
 ****************************************************************************/
72
int E_(MMSHOpen)( access_t *p_access )
73 74
{
    access_sys_t    *p_sys;
75
    char            *psz_location = NULL;
76
    char            *psz_proxy;
Laurent Aimar's avatar
Laurent Aimar committed
77

78 79
    /* init p_sys */

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    /* Set up p_access */
    p_access->pf_read = Read;
    p_access->pf_block = NULL;
    p_access->pf_control = Control;
    p_access->pf_seek = Seek;
    p_access->info.i_update = 0;
    p_access->info.i_size = 0;
    p_access->info.i_pos = 0;
    p_access->info.b_eof = VLC_FALSE;
    p_access->info.i_title = 0;
    p_access->info.i_seekpoint = 0;
    p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
    memset( p_sys, 0, sizeof( access_sys_t ) );
    p_sys->i_proto= MMS_PROTO_HTTP;
    p_sys->fd     = -1;
95
    p_sys->i_start= 0;
96

97 98 99 100 101 102 103
    /* Handle proxy */
    p_sys->b_proxy = VLC_FALSE;
    memset( &p_sys->proxy, 0, sizeof(p_sys->proxy) );

    /* Check proxy */
    /* TODO reuse instead http-proxy from http access ? */
    psz_proxy = var_CreateGetString( p_access, "mmsh-proxy" );
104 105 106 107 108 109 110 111 112 113 114 115 116 117
    if( !*psz_proxy )
    {
        char *psz_http_proxy = config_GetPsz( p_access, "http-proxy" );
        if( psz_http_proxy && *psz_http_proxy )
        {
            free( psz_proxy );
            psz_proxy = psz_http_proxy;
            var_SetString( p_access, "mmsh-proxy", psz_proxy );
        }
        else
        {
            free( psz_http_proxy );
        }
    }
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    if( *psz_proxy )
    {
        p_sys->b_proxy = VLC_TRUE;
        vlc_UrlParse( &p_sys->proxy, psz_proxy, 0 );
    }
#ifdef HAVE_GETENV
    else
    {
        char *psz_proxy = getenv( "http_proxy" );
        if( psz_proxy && *psz_proxy )
        {
            p_sys->b_proxy = VLC_TRUE;
            vlc_UrlParse( &p_sys->proxy, psz_proxy, 0 );
        }
    }
#endif
    free( psz_proxy );

    if( p_sys->b_proxy )
    {
       if( p_sys->proxy.psz_host == NULL || *p_sys->proxy.psz_host == '\0' )
        {
            msg_Warn( p_access, "invalid proxy host" );
            vlc_UrlClean( &p_sys->proxy );
            free( p_sys );
            return VLC_EGENERIC;
        }
        if( p_sys->proxy.i_port <= 0 )
            p_sys->proxy.i_port = 80;
        msg_Dbg( p_access, "Using http proxy %s:%d",
                 p_sys->proxy.psz_host, p_sys->proxy.i_port );
    }

151
    /* open a tcp connection */
152 153
    vlc_UrlParse( &p_sys->url, p_access->psz_path, 0 );
    if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
154
    {
155
        msg_Err( p_access, "invalid host" );
Laurent Aimar's avatar
Laurent Aimar committed
156
        vlc_UrlClean( &p_sys->proxy );
157 158 159
        vlc_UrlClean( &p_sys->url );
        free( p_sys );
        return VLC_EGENERIC;
160
    }
161 162
    if( p_sys->url.i_port <= 0 )
        p_sys->url.i_port = 80;
163

164
    if( Describe( p_access, &psz_location ) )
165
    {
Laurent Aimar's avatar
Laurent Aimar committed
166
        vlc_UrlClean( &p_sys->proxy );
167 168 169
        vlc_UrlClean( &p_sys->url );
        free( p_sys );
        return VLC_EGENERIC;
170
    }
171 172
    /* Handle redirection */
    if( psz_location && *psz_location )
173
    {
174
        playlist_t * p_playlist = pl_Yield( p_access );
175
        msg_Dbg( p_access, "redirection to %s", psz_location );
176

177
        /** \bug we do not autodelete here */
178
        playlist_Add( p_playlist, psz_location, psz_location,
179 180
                      PLAYLIST_INSERT | PLAYLIST_GO, PLAYLIST_END, VLC_TRUE,
                      VLC_FALSE );
181 182
        vlc_object_release( p_playlist );

183 184
        free( psz_location );

185
        p_access->pf_read = ReadRedirect;
186 187 188
        return VLC_SUCCESS;
    }

189
    /* Start playing */
190
    if( Start( p_access, 0 ) )
191
    {
192
        msg_Err( p_access, "cannot start stream" );
193
        free( p_sys->p_header );
Laurent Aimar's avatar
Laurent Aimar committed
194
        vlc_UrlClean( &p_sys->proxy );
195 196 197
        vlc_UrlClean( &p_sys->url );
        free( p_sys );
        return VLC_EGENERIC;
198 199
    }

200
    if( !p_sys->b_broadcast )
201
    {
202
        p_access->info.i_size = p_sys->asfh.i_file_size;
203 204
    }

Laurent Aimar's avatar
Laurent Aimar committed
205
    return VLC_SUCCESS;
206 207 208 209 210
}

/*****************************************************************************
 * Close: free unused data structures
 *****************************************************************************/
211 212 213 214 215
void E_( MMSHClose )( access_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;

    Stop( p_access );
Laurent Aimar's avatar
Laurent Aimar committed
216 217 218 219 220 221

    if( p_sys->p_header )
        free( p_sys->p_header  );

    vlc_UrlClean( &p_sys->proxy );
    vlc_UrlClean( &p_sys->url );
222 223 224 225 226 227 228
    free( p_sys );
}

/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
229
{
230 231 232 233
    access_sys_t *p_sys = p_access->p_sys;
    vlc_bool_t   *pb_bool;
    int          *pi_int;
    int64_t      *pi_64;
234
    int          i_int;
235 236 237 238 239 240 241 242

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = !p_sys->b_broadcast;
            break;
243

244 245
        case ACCESS_CAN_FASTSEEK:
        case ACCESS_CAN_PAUSE:
246 247 248 249
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_FALSE;
            break;

250 251
        case ACCESS_CAN_CONTROL_PACE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
252 253 254

#if 0       /* Disable for now until we have a clock synchro algo
             * which works with something else than MPEG over UDP */
255
            *pb_bool = VLC_FALSE;
256 257
#endif
            *pb_bool = VLC_TRUE;
258
            break;
259

260 261 262 263 264
        /* */
        case ACCESS_GET_MTU:
            pi_int = (int*)va_arg( args, int * );
            *pi_int = 3 * p_sys->asfh.i_min_data_packet_size;
            break;
265

266 267
        case ACCESS_GET_PTS_DELAY:
            pi_64 = (int64_t*)va_arg( args, int64_t * );
268
            *pi_64 = (int64_t)var_GetInteger( p_access, "mms-caching" ) * I64C(1000);
269
            break;
270

271 272
        case ACCESS_GET_PRIVATE_ID_STATE:
            i_int = (int)va_arg( args, int );
273
            pb_bool = (vlc_bool_t *)va_arg( args, vlc_bool_t * );
274 275 276 277 278 279

            if( i_int < 0 || i_int > 127 )
                return VLC_EGENERIC;
            *pb_bool =  p_sys->asfh.stream[i_int].i_selected ? VLC_TRUE : VLC_FALSE;
            break;

280 281 282 283 284
        /* */
        case ACCESS_SET_PAUSE_STATE:
        case ACCESS_GET_TITLE_INFO:
        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
285
        case ACCESS_SET_PRIVATE_ID_STATE:
286
            return VLC_EGENERIC;
287

288
        default:
289
            msg_Warn( p_access, "unimplemented query in control" );
290 291 292 293
            return VLC_EGENERIC;

    }
    return VLC_SUCCESS;
294 295 296 297 298
}

/*****************************************************************************
 * Seek: try to go at the right place
 *****************************************************************************/
299
static int Seek( access_t *p_access, int64_t i_pos )
300
{
301
    access_sys_t *p_sys = p_access->p_sys;
302 303 304 305
    chunk_t      ck;
    off_t        i_offset;
    off_t        i_packet;

306 307
    msg_Dbg( p_access, "seeking to "I64Fd, i_pos );

308 309 310
    i_packet = ( i_pos - p_sys->i_header ) / p_sys->asfh.i_min_data_packet_size;
    i_offset = ( i_pos - p_sys->i_header ) % p_sys->asfh.i_min_data_packet_size;

311 312
    Stop( p_access );
    Start( p_access, i_packet * p_sys->asfh.i_min_data_packet_size );
313

314
    while( !p_access->b_die )
315
    {
316
        if( GetPacket( p_access, &ck ) )
317 318 319 320 321
            break;

        /* skip headers */
        if( ck.i_type != 0x4824 )
            break;
322 323

        msg_Warn( p_access, "skipping header" );
324 325
    }

326 327
    p_access->info.i_pos = i_pos;
    p_access->info.b_eof = VLC_FALSE;
328 329
    p_sys->i_packet_used += i_offset;

330
    return VLC_SUCCESS;
331 332
}

333 334 335
/*****************************************************************************
 * Read:
 *****************************************************************************/
336
static int ReadRedirect( access_t *p_access, uint8_t *p, int i_len )
337 338 339 340
{
    return 0;
}

341 342 343
/*****************************************************************************
 * Read:
 *****************************************************************************/
344
static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
345
{
346
    access_sys_t *p_sys = p_access->p_sys;
347 348 349
    size_t       i_copy;
    size_t       i_data = 0;

350 351 352
    if( p_access->info.b_eof )
        return 0;

353
    while( i_data < (size_t) i_len )
354
    {
355
        if( p_access->info.i_pos < p_sys->i_start + p_sys->i_header )
Laurent Aimar's avatar
Laurent Aimar committed
356
        {
357
            int i_offset = p_access->info.i_pos - p_sys->i_start;
358
            i_copy = __MIN( p_sys->i_header - i_offset, (int)((size_t)i_len - i_data) );
359
            memcpy( &p_buffer[i_data], &p_sys->p_header[i_offset], i_copy );
Laurent Aimar's avatar
Laurent Aimar committed
360 361

            i_data += i_copy;
362
            p_access->info.i_pos += i_copy;
Laurent Aimar's avatar
Laurent Aimar committed
363 364
        }
        else if( p_sys->i_packet_used < p_sys->i_packet_length )
365
        {
366 367
            i_copy = __MIN( p_sys->i_packet_length - p_sys->i_packet_used,
                            i_len - i_data );
368 369 370 371 372 373 374

            memcpy( &p_buffer[i_data],
                    &p_sys->p_packet[p_sys->i_packet_used],
                    i_copy );

            i_data += i_copy;
            p_sys->i_packet_used += i_copy;
375
            p_access->info.i_pos += i_copy;
376
        }
377 378
        else if( (p_sys->i_packet_length > 0) &&
                 ((int)p_sys->i_packet_used < p_sys->asfh.i_min_data_packet_size) )
379
        {
380 381
            i_copy = __MIN( p_sys->asfh.i_min_data_packet_size - p_sys->i_packet_used,
                            i_len - i_data );
382 383 384 385 386

            memset( &p_buffer[i_data], 0, i_copy );

            i_data += i_copy;
            p_sys->i_packet_used += i_copy;
387
            p_access->info.i_pos += i_copy;
388 389 390 391
        }
        else
        {
            chunk_t ck;
392
            if( GetPacket( p_access, &ck ) )
393
            {
394 395
                int i_ret = -1;
                if( p_sys->b_broadcast )
396
                {
397 398 399 400
                    if( ck.i_type == 0x4524 && ck.i_sequence != 0 )
                        i_ret = Restart( p_access );
                    else if( ck.i_type == 0x4324 )
                        i_ret = Reset( p_access );
401
                }
402
                if( i_ret )
403
                {
404 405
                    p_access->info.b_eof = VLC_TRUE;
                    return 0;
406
                }
407
            }
408
            if( ck.i_type != 0x4424 )
Laurent Aimar's avatar
Laurent Aimar committed
409 410 411 412
            {
                p_sys->i_packet_used = 0;
                p_sys->i_packet_length = 0;
            }
413 414 415 416 417 418
        }
    }

    return( i_data );
}

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
/* */
static int Restart( access_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;
    char *psz_location = NULL;

    msg_Dbg( p_access, "Restart the stream" );
    p_sys->i_start = p_access->info.i_pos;

    /* */
    msg_Dbg( p_access, "stoping the stream" );
    Stop( p_access );

    /* */
    msg_Dbg( p_access, "describe the stream" );
    if( Describe( p_access, &psz_location ) )
    {
        msg_Err( p_access, "describe failed" );
        return -1;
    }
    /* */
    if( Start( p_access, 0 ) )
    {
        msg_Err( p_access, "Start failed" );
        return -1;
    }
    return 0;
}
static int Reset( access_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;
    asf_header_t old_asfh = p_sys->asfh;
    int i;

    msg_Dbg( p_access, "Reset the stream" );
    p_sys->i_start = p_access->info.i_pos;

    /* */
    p_sys->i_packet_sequence = 0;
    p_sys->i_packet_used = 0;
    p_sys->i_packet_length = 0;
    p_sys->p_packet = NULL;

    /* Get the next header FIXME memory loss ? */
    GetHeader( p_access );
    if( p_sys->i_header <= 0 )
        return -1;

    E_( asf_HeaderParse )( &p_sys->asfh,
                           p_sys->p_header, p_sys->i_header );
    msg_Dbg( p_access, "packet count="I64Fd" packet size=%d",
             p_sys->asfh.i_data_packets_count,
             p_sys->asfh.i_min_data_packet_size );

    E_( asf_StreamSelect)( &p_sys->asfh,
                           var_CreateGetInteger( p_access, "mms-maxbitrate" ),
                           var_CreateGetInteger( p_access, "mms-all" ),
                           var_CreateGetInteger( p_access, "audio" ),
                           var_CreateGetInteger( p_access, "video" ) );

    /* Check we have comptible asfh */
    for( i = 1; i < 128; i++ )
    {
        asf_stream_t *p_old = &old_asfh.stream[i];
        asf_stream_t *p_new = &p_sys->asfh.stream[i];

        if( p_old->i_cat != p_new->i_cat || p_old->i_selected != p_new->i_selected )
            break;
    }
    if( i < 128 )
    {
        msg_Warn( p_access, "incompatible asf header, restart" );
        return Restart( p_access );
    }

    /* */
    p_sys->i_packet_used = 0;
    p_sys->i_packet_length = 0;
    return 0;
}
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547

static int OpenConnection( access_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;
    vlc_url_t    srv = p_sys->b_proxy ? p_sys->proxy : p_sys->url;

    if( ( p_sys->fd = net_ConnectTCP( p_access,
                                      srv.psz_host, srv.i_port ) ) < 0 )
    {
        msg_Err( p_access, "cannot connect to %s:%d",
                 srv.psz_host, srv.i_port );
        return VLC_EGENERIC;
    }

    if( p_sys->b_proxy )
    {
        net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
                    "GET http://%s:%d%s HTTP/1.0\r\n",
                    p_sys->url.psz_host, p_sys->url.i_port,
                    ( p_sys->url.psz_path == NULL || *p_sys->url.psz_path == '\0' ) ? "/" : p_sys->url.psz_path );

        /* Proxy Authentication */
        if( p_sys->proxy.psz_username && *p_sys->proxy.psz_username )
        {
            char *buf;
            char *b64;

            asprintf( &buf, "%s:%s", p_sys->proxy.psz_username,
                       p_sys->proxy.psz_password ? p_sys->proxy.psz_password : "" );

            b64 = vlc_b64_encode( buf );
            free( buf );

            net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
                        "Proxy-Authorization: Basic %s\r\n", b64 );
            free( b64 );
        }
    }
    else
    {
        net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
                    "GET %s HTTP/1.0\r\n"
                    "Host: %s:%d\r\n",
                    ( p_sys->url.psz_path == NULL || *p_sys->url.psz_path == '\0' ) ? "/" : p_sys->url.psz_path,
                    p_sys->url.psz_host, p_sys->url.i_port );
    }
    return VLC_SUCCESS;
}

548 549 550
/*****************************************************************************
 * Describe:
 *****************************************************************************/
551
static int Describe( access_t  *p_access, char **ppsz_location )
552
{
553
    access_sys_t *p_sys = p_access->p_sys;
554 555 556 557 558 559 560 561 562 563 564 565 566
    char         *psz_location = NULL;
    char         *psz;
    int          i_code;

    /* Reinit context */
    p_sys->b_broadcast = VLC_TRUE;
    p_sys->i_request_context = 1;
    p_sys->i_packet_sequence = 0;
    p_sys->i_packet_used = 0;
    p_sys->i_packet_length = 0;
    p_sys->p_packet = NULL;
    E_( GenerateGuid )( &p_sys->guid );

567 568
    if( OpenConnection( p_access ) )
        return VLC_EGENERIC;
569

570
    net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
571
                "Accept: */*\r\n"
572
                "User-Agent: "MMSH_USER_AGENT"\r\n"
573 574 575 576 577 578
                "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%d,max-duration=0\r\n"
                "Pragma: xClientGUID={"GUID_FMT"}\r\n"
                "Connection: Close\r\n",
                p_sys->i_request_context++,
                GUID_PRINT( p_sys->guid ) );

579
    if( net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL, "\r\n" ) < 0 )
580
    {
581
        msg_Err( p_access, "failed to send request" );
582 583 584 585
        goto error;
    }

    /* Receive the http header */
586
    if( ( psz = net_Gets( VLC_OBJECT(p_access), p_sys->fd, NULL ) ) == NULL )
587
    {
588
        msg_Err( p_access, "failed to read answer" );
589 590 591 592
        goto error;
    }
    if( strncmp( psz, "HTTP/1.", 7 ) )
    {
593
        msg_Err( p_access, "invalid HTTP reply '%s'", psz );
594 595 596 597 598 599
        free( psz );
        goto error;
    }
    i_code = atoi( &psz[9] );
    if( i_code >= 400 )
    {
600
        msg_Err( p_access, "error: %s", psz );
601 602 603 604
        free( psz );
        goto error;
    }

605
    msg_Dbg( p_access, "HTTP reply '%s'", psz );
606 607 608
    free( psz );
    for( ;; )
    {
609
        char *psz = net_Gets( p_access, p_sys->fd, NULL );
610 611 612 613
        char *p;

        if( psz == NULL )
        {
614
            msg_Err( p_access, "failed to read answer" );
615 616 617 618 619 620 621 622 623 624 625
            goto error;
        }

        if( *psz == '\0' )
        {
            free( psz );
            break;
        }

        if( ( p = strchr( psz, ':' ) ) == NULL )
        {
626
            msg_Err( p_access, "malformed header line: %s", psz );
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
            free( psz );
            goto error;
        }
        *p++ = '\0';
        while( *p == ' ' ) p++;

        /* FIXME FIXME test Content-Type to see if it's a plain stream or an
         * asx FIXME */
        if( !strcasecmp( psz, "Pragma" ) )
        {
            if( strstr( p, "features" ) )
            {
                /* FIXME, it is a bit badly done here ..... */
                if( strstr( p, "broadcast" ) )
                {
642
                    msg_Dbg( p_access, "stream type = broadcast" );
643 644 645 646
                    p_sys->b_broadcast = VLC_TRUE;
                }
                else if( strstr( p, "seekable" ) )
                {
647
                    msg_Dbg( p_access, "stream type = seekable" );
648 649 650 651
                    p_sys->b_broadcast = VLC_FALSE;
                }
                else
                {
652
                    msg_Warn( p_access, "unknow stream types (%s)", p );
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
                    p_sys->b_broadcast = VLC_FALSE;
                }
            }
        }
        else if( !strcasecmp( psz, "Location" ) )
        {
            psz_location = strdup( p );
        }

        free( psz );
    }

    /* Handle the redirection */
    if( ( i_code == 301 || i_code == 302 ||
          i_code == 303 || i_code == 307 ) &&
        psz_location && *psz_location )
    {
670
        msg_Dbg( p_access, "redirection to %s", psz_location );
671 672 673 674 675 676 677
        net_Close( p_sys->fd ); p_sys->fd = -1;

        *ppsz_location = psz_location;
        return VLC_SUCCESS;
    }

    /* Read the asf header */
678
    GetHeader( p_access );
679 680
    if( p_sys->i_header <= 0 )
    {
681
        msg_Err( p_access, "header size == 0" );
682 683 684 685 686 687 688 689 690 691 692 693
        goto error;
    }
    /* close this connection */
    net_Close( p_sys->fd ); p_sys->fd = -1;

    /* *** parse header and get stream and their id *** */
    /* get all streams properties,
     *
     * TODO : stream bitrates properties(optional)
     *        and bitrate mutual exclusion(optional) */
    E_( asf_HeaderParse )( &p_sys->asfh,
                           p_sys->p_header, p_sys->i_header );
694
    msg_Dbg( p_access, "packet count="I64Fd" packet size=%d",
695 696 697 698
             p_sys->asfh.i_data_packets_count,
             p_sys->asfh.i_min_data_packet_size );

    E_( asf_StreamSelect)( &p_sys->asfh,
699 700 701 702
                           var_CreateGetInteger( p_access, "mms-maxbitrate" ),
                           var_CreateGetInteger( p_access, "mms-all" ),
                           var_CreateGetInteger( p_access, "audio" ),
                           var_CreateGetInteger( p_access, "video" ) );
703 704 705 706 707 708 709 710 711 712
    return VLC_SUCCESS;

error:
    if( p_sys->fd > 0 )
    {
        net_Close( p_sys->fd  );
        p_sys->fd = -1;
    }
    return VLC_EGENERIC;
}
713

714 715 716 717 718 719
static void GetHeader( access_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;

    /* Read the asf header */
    p_sys->i_header = 0;
Laurent Aimar's avatar
Laurent Aimar committed
720 721
    if( p_sys->p_header )
        free( p_sys->p_header  );
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
    p_sys->p_header = NULL;
    for( ;; )
    {
        chunk_t ck;
        if( GetPacket( p_access, &ck ) || ck.i_type != 0x4824 )
            break;

        if( ck.i_data > 0 )
        {
            p_sys->i_header += ck.i_data;
            p_sys->p_header = realloc( p_sys->p_header, p_sys->i_header );
            memcpy( &p_sys->p_header[p_sys->i_header - ck.i_data],
                    ck.p_data, ck.i_data );
        }
    }
    msg_Dbg( p_access, "complete header size=%d", p_sys->i_header );
}

740

Laurent Aimar's avatar
Laurent Aimar committed
741 742 743
/*****************************************************************************
 *
 *****************************************************************************/
744
static int Start( access_t *p_access, off_t i_pos )
745
{
746
    access_sys_t *p_sys = p_access->p_sys;
747
    int  i_streams = 0;
748
    int  i_streams_selected = 0;
749 750
    int  i;
    char *psz;
751

752
    msg_Dbg( p_access, "starting stream" );
753 754 755

    for( i = 1; i < 128; i++ )
    {
756 757 758
        if( p_sys->asfh.stream[i].i_cat == ASF_STREAM_UNKNOWN )
            continue;
        i_streams++;
759
        if( p_sys->asfh.stream[i].i_selected )
760
            i_streams_selected++;
761
    }
762
    if( i_streams_selected <= 0 )
763
    {
764
        msg_Err( p_access, "no stream selected" );
765 766
        return VLC_EGENERIC;
    }
767

768 769
    if( OpenConnection( p_access ) )
        return VLC_EGENERIC;
770

771
    net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
772
                "Accept: */*\r\n"
773
                "User-Agent: "MMSH_USER_AGENT"\r\n" );
774 775
    if( p_sys->b_broadcast )
    {
776
        net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
777 778
                    "Pragma: no-cache,rate=1.000000,request-context=%d\r\n",
                    p_sys->i_request_context++ );
779 780 781
    }
    else
    {
782
        net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
783 784 785 786
                    "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=%u:%u,request-context=%d,max-duration=0\r\n",
                    (uint32_t)((i_pos >> 32)&0xffffffff),
                    (uint32_t)(i_pos&0xffffffff),
                    p_sys->i_request_context++ );
787
    }
788
    net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
789 790 791 792 793 794
                "Pragma: xPlayStrm=1\r\n"
                "Pragma: xClientGUID={"GUID_FMT"}\r\n"
                "Pragma: stream-switch-count=%d\r\n"
                "Pragma: stream-switch-entry=",
                GUID_PRINT( p_sys->guid ),
                i_streams);
795

796
    for( i = 1; i < 128; i++ )
797
    {
798
        if( p_sys->asfh.stream[i].i_cat != ASF_STREAM_UNKNOWN )
799
        {
800 801
            int i_select = 2;
            if( p_sys->asfh.stream[i].i_selected )
802
            {
803
                i_select = 0;
804 805
            }

806
            net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
807
                        "ffff:%d:%d ", i, i_select );
808 809
        }
    }
810 811 812
    net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL, "\r\n" );
    net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL,
                "Connection: Close\r\n" );
813

814
    if( net_Printf( VLC_OBJECT(p_access), p_sys->fd, NULL, "\r\n" ) < 0 )
815
    {
816
        msg_Err( p_access, "failed to send request" );
817 818 819
        return VLC_EGENERIC;
    }

820
    if( ( psz = net_Gets( VLC_OBJECT(p_access), p_sys->fd, NULL ) ) == NULL )
821
    {
822
        msg_Err( p_access, "cannot read data" );
823 824
        return VLC_EGENERIC;
    }
825
    if( atoi( &psz[9] ) >= 400 )
826
    {
827
        msg_Err( p_access, "error: %s", psz );
828 829
        free( psz );
        return VLC_EGENERIC;
830
    }
831
    msg_Dbg( p_access, "HTTP reply '%s'", psz );
832 833 834 835
    free( psz );

    /* FIXME check HTTP code */
    for( ;; )
836
    {
837
        char *psz = net_Gets( p_access, p_sys->fd, NULL );
838 839
        if( psz == NULL )
        {
840
            msg_Err( p_access, "cannot read data" );
841 842 843 844 845 846 847
            return VLC_EGENERIC;
        }
        if( *psz == '\0' )
        {
            free( psz );
            break;
        }
848
        msg_Dbg( p_access, "%s", psz );
849
        free( psz );
850 851
    }

Laurent Aimar's avatar
Laurent Aimar committed
852 853 854
    p_sys->i_packet_used   = 0;
    p_sys->i_packet_length = 0;

855 856 857
    return VLC_SUCCESS;
}

Laurent Aimar's avatar
Laurent Aimar committed
858 859 860
/*****************************************************************************
 *
 *****************************************************************************/
861
static void Stop( access_t *p_access )
862
{
863
    access_sys_t *p_sys = p_access->p_sys;
864

865
    msg_Dbg( p_access, "closing stream" );
866 867 868 869 870
    if( p_sys->fd > 0 )
    {
        net_Close( p_sys->fd );
        p_sys->fd = -1;
    }
871 872
}

Laurent Aimar's avatar
Laurent Aimar committed
873 874 875
/*****************************************************************************
 *
 *****************************************************************************/
876
static int GetPacket( access_t * p_access, chunk_t *p_ck )
877
{
878
    access_sys_t *p_sys = p_access->p_sys;
879
    int restsize;
880

881 882 883
    /* chunk_t */
    memset( p_ck, 0, sizeof( chunk_t ) );

884
    /* Read the chunk header */
885 886 887 888 889 890 891
    /* Some headers are short, like 0x4324. Reading 12 bytes will cause us
     * to lose synchronization with the stream. Just read to the length
     * (4 bytes), decode and then read up to 8 additional bytes to get the
     * entire header.
     */
    if( net_Read( p_access, p_sys->fd, NULL, p_sys->buffer, 4, VLC_TRUE ) < 4 )
       return VLC_EGENERIC;
892

893 894
    p_ck->i_type = GetWLE( p_sys->buffer);
    p_ck->i_size = GetWLE( p_sys->buffer + 2);
895

896 897 898 899 900 901 902 903 904
    restsize = p_ck->i_size;
    if( restsize > 8 )
        restsize = 8;

    if( net_Read( p_access, p_sys->fd, NULL, p_sys->buffer + 4, restsize, VLC_TRUE ) < restsize )
    {
        msg_Err( p_access, "cannot read data" );
        return VLC_EGENERIC;
    }
905 906
    p_ck->i_sequence  = GetDWLE( p_sys->buffer + 4);
    p_ck->i_unknown   = GetWLE( p_sys->buffer + 8);
907 908 909 910 911 912 913 914 915 916

    /* Set i_size2 to 8 if this header was short, since a real value won't be
     * present in the buffer. Using 8 avoid reading additional data for the
     * packet.
     */
    if( restsize < 8 )
        p_ck->i_size2 = 8;
    else
        p_ck->i_size2 = GetWLE( p_sys->buffer + 10);

917 918
    p_ck->p_data      = p_sys->buffer + 12;
    p_ck->i_data      = p_ck->i_size2 - 8;
919

Laurent Aimar's avatar
Laurent Aimar committed
920
    if( p_ck->i_type == 0x4524 )   // Transfer complete
921
    {
922 923
        if( p_ck->i_sequence == 0 )
        {
924
            msg_Warn( p_access, "EOF" );
925 926 927 928
            return VLC_EGENERIC;
        }
        else
        {
929
            msg_Warn( p_access, "next stream following" );
930 931
            return VLC_EGENERIC;
        }
932
    }
933 934 935 936 937 938 939
    else if( p_ck->i_type == 0x4324 )
    {
        /* 0x4324 is CHUNK_TYPE_RESET: a new stream will follow with a sequence of 0 */
        msg_Warn( p_access, "next stream following (reset) seq=%d", p_ck->i_sequence  );
        return VLC_EGENERIC;
    }
    else if( (p_ck->i_type != 0x4824) && (p_ck->i_type != 0x4424) )
940
    {
941
        msg_Err( p_access, "invalid chunk FATAL (0x%x)", p_ck->i_type );
Laurent Aimar's avatar
Laurent Aimar committed
942
        return VLC_EGENERIC;
943 944
    }

945 946 947
    if( (p_ck->i_data > 0) &&
        (net_Read( p_access, p_sys->fd, NULL, &p_sys->buffer[12],
                   p_ck->i_data, VLC_TRUE ) < p_ck->i_data) )
948
    {
949
        msg_Err( p_access, "cannot read data" );
950
        return VLC_EGENERIC;
951 952
    }

953
#if 0
954 955
    if( (p_sys->i_packet_sequence != 0) &&
        (p_ck->i_sequence != p_sys->i_packet_sequence) )
956
    {
957
        msg_Warn( p_access, "packet lost ? (%d != %d)", p_ck->i_sequence, p_sys->i_packet_sequence );
958
    }
959
#endif
960

Laurent Aimar's avatar
Laurent Aimar committed
961 962 963 964
    p_sys->i_packet_sequence = p_ck->i_sequence + 1;
    p_sys->i_packet_used   = 0;
    p_sys->i_packet_length = p_ck->i_data;
    p_sys->p_packet        = p_ck->p_data;
965

Laurent Aimar's avatar
Laurent Aimar committed
966
    return VLC_SUCCESS;
967
}