mirror of
https://github.com/mpv-player/mpv
synced 2024-10-30 04:46:41 +01:00
dd96c11d5e
The "http:" protocol has been switched to use ffmpeg's HTTP implementation some time ago. One problem with this was that many HTTP specific options stopped working, because they were obviously implemented for the internal HTTP implementation only. Add the missing things. Note that many options will work for ffmpeg only, as Libav's HTTP implementation is missing these. They will silently be ignored on Libav. Some options we can't fix: --ipv4-only-proxy, --prefer-ipv4, --prefer-ipv6 As far as I can see, not even libavformat internals distinguish between ipv4 and ipv6. --user, --passwd ffmpeg probably supports specifying these in the URL directly.
856 lines
26 KiB
C
856 lines
26 KiB
C
/*
|
|
* This file is part of MPlayer.
|
|
*
|
|
* MPlayer 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.
|
|
*
|
|
* MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
#include <libavutil/intreadwrite.h>
|
|
|
|
#include "config.h"
|
|
#include "core/mp_msg.h"
|
|
#include "core/options.h"
|
|
|
|
#if HAVE_WINSOCK2_H
|
|
#include <winsock2.h>
|
|
#endif
|
|
|
|
#include "url.h"
|
|
#include "http.h"
|
|
#include "demux/asf.h"
|
|
|
|
#include "stream.h"
|
|
#include "demux/demux.h"
|
|
#include "asf_mmst_streaming.h"
|
|
#include "network.h"
|
|
#include "tcp.h"
|
|
|
|
#include "demux/asfguid.h"
|
|
|
|
static int asf_http_streaming_start(stream_t *stream, int *demuxer_type);
|
|
|
|
static int asf_read_wrapper(int fd, void *buffer, int len, streaming_ctrl_t *stream_ctrl) {
|
|
uint8_t *buf = buffer;
|
|
while (len > 0) {
|
|
int got = nop_streaming_read(fd, buf, len, stream_ctrl);
|
|
if (got <= 0) {
|
|
mp_tmsg(MSGT_NETWORK, MSGL_ERR, "Error while reading network stream.\n");
|
|
return got;
|
|
}
|
|
buf += got;
|
|
len -= got;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// We can try several protocol for asf streaming
|
|
// * First we can try TCP, but if there is a proxy for
|
|
// internet connection, the TCP connection will not get
|
|
// through
|
|
// * Then we can try HTTP.
|
|
//
|
|
// Note: Using WMP sequence MMST and then HTTP.
|
|
|
|
static int asf_streaming_start( stream_t *stream, int *demuxer_type) {
|
|
char *proto = stream->streaming_ctrl->url->protocol;
|
|
int fd = -1;
|
|
int port = stream->streaming_ctrl->url->port;
|
|
|
|
//Is protocol mms or mmst?
|
|
if (!strcasecmp(proto, "mp_mmst") || !strcasecmp(proto, "mp_mms"))
|
|
{
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"Trying ASF/TCP...\n");
|
|
fd = asf_mmst_streaming_start( stream );
|
|
stream->streaming_ctrl->url->port = port;
|
|
if( fd>-1 ) return fd;
|
|
mp_msg(MSGT_NETWORK,MSGL_V," ===> ASF/TCP failed\n");
|
|
if( fd==-2 ) return -1;
|
|
}
|
|
|
|
//Is protocol http, http_proxy, or mms?
|
|
if (!strcasecmp(proto, "mp_http_proxy") || !strcasecmp(proto, "mp_http") ||
|
|
!strcasecmp(proto, "mp_mms") || !strcasecmp(proto, "mp_mmsh") ||
|
|
!strcasecmp(proto, "mp_mmshttp"))
|
|
{
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"Trying ASF/HTTP...\n");
|
|
fd = asf_http_streaming_start( stream, demuxer_type );
|
|
stream->streaming_ctrl->url->port = port;
|
|
if( fd>-1 ) return fd;
|
|
mp_msg(MSGT_NETWORK,MSGL_V," ===> ASF/HTTP failed\n");
|
|
if( fd==-2 ) return -1;
|
|
}
|
|
|
|
//everything failed
|
|
return -1;
|
|
}
|
|
|
|
static int asf_streaming(ASF_stream_chunck_t *stream_chunck, int *drop_packet ) {
|
|
/*
|
|
printf("ASF stream chunck size=%d\n", stream_chunck->size);
|
|
printf("length: %d\n", length );
|
|
printf("0x%02X\n", stream_chunck->type );
|
|
*/
|
|
if( drop_packet!=NULL ) *drop_packet = 0;
|
|
|
|
if( stream_chunck->size<8 ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Ahhhh, stream_chunck size is too small: %d\n", stream_chunck->size);
|
|
return -1;
|
|
}
|
|
if( stream_chunck->size!=stream_chunck->size_confirm ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"size_confirm mismatch!: %d %d\n", stream_chunck->size, stream_chunck->size_confirm);
|
|
return -1;
|
|
}
|
|
/*
|
|
printf(" type: 0x%02X\n", stream_chunck->type );
|
|
printf(" size: %d (0x%02X)\n", stream_chunck->size, stream_chunck->size );
|
|
printf(" sequence_number: 0x%04X\n", stream_chunck->sequence_number );
|
|
printf(" unknown: 0x%02X\n", stream_chunck->unknown );
|
|
printf(" size_confirm: 0x%02X\n", stream_chunck->size_confirm );
|
|
*/
|
|
switch(stream_chunck->type) {
|
|
case ASF_STREAMING_CLEAR: // $C Clear ASF configuration
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> Clearing ASF stream configuration!\n");
|
|
if( drop_packet!=NULL ) *drop_packet = 1;
|
|
return stream_chunck->size;
|
|
break;
|
|
case ASF_STREAMING_DATA: // $D Data follows
|
|
// printf("=====> Data follows\n");
|
|
break;
|
|
case ASF_STREAMING_END_TRANS: // $E Transfer complete
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> Transfer complete\n");
|
|
if( drop_packet!=NULL ) *drop_packet = 1;
|
|
return stream_chunck->size;
|
|
break;
|
|
case ASF_STREAMING_HEADER: // $H ASF header chunk follows
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF header chunk follows\n");
|
|
break;
|
|
default:
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> Unknown stream type 0x%x\n", stream_chunck->type );
|
|
}
|
|
return stream_chunck->size+4;
|
|
}
|
|
|
|
static void close_s(stream_t *stream) {
|
|
closesocket(stream->fd);
|
|
stream->fd=-1;
|
|
}
|
|
|
|
static int max_idx(int s_count, int *s_rates, int bound) {
|
|
int i, best = -1, rate = -1;
|
|
for (i = 0; i < s_count; i++) {
|
|
if (s_rates[i] > rate && s_rates[i] <= bound) {
|
|
rate = s_rates[i];
|
|
best = i;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
static int asf_streaming_parse_header(stream_t *s, int fd) {
|
|
streaming_ctrl_t* streaming_ctrl = s->streaming_ctrl;
|
|
ASF_stream_chunck_t chunk;
|
|
asf_http_streaming_ctrl_t* asf_ctrl = streaming_ctrl->data;
|
|
char* buffer=NULL, *chunk_buffer=NULL;
|
|
int i,r,size,pos = 0;
|
|
int start;
|
|
int buffer_size = 0;
|
|
int chunk_size2read = 0;
|
|
int bw = streaming_ctrl->bandwidth;
|
|
int *v_rates = NULL, *a_rates = NULL;
|
|
int v_rate = 0, a_rate = 0, a_idx = -1, v_idx = -1;
|
|
|
|
if(asf_ctrl == NULL) return -1;
|
|
|
|
// The ASF header can be in several network chunks. For example if the content description
|
|
// is big, the ASF header will be split in 2 network chunk.
|
|
// So we need to retrieve all the chunk before starting to parse the header.
|
|
do {
|
|
if (asf_read_wrapper(fd, &chunk, sizeof(ASF_stream_chunck_t), streaming_ctrl) <= 0)
|
|
return -1;
|
|
// Endian handling of the stream chunk
|
|
le2me_ASF_stream_chunck_t(&chunk);
|
|
size = asf_streaming( &chunk, &r) - sizeof(ASF_stream_chunck_t);
|
|
if(r) mp_tmsg(MSGT_NETWORK,MSGL_WARN,"Warning: drop header ????\n");
|
|
if(size < 0){
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Error while parsing chunk header\n");
|
|
return -1;
|
|
}
|
|
if (chunk.type != ASF_STREAMING_HEADER) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Didn't get a header as first chunk !!!!\n");
|
|
return -1;
|
|
}
|
|
|
|
// audit: do not overflow buffer_size
|
|
if (size > SIZE_MAX - buffer_size) return -1;
|
|
buffer = malloc(size+buffer_size);
|
|
if(buffer == NULL) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_FATAL,"Error: Can't allocate %d bytes buffer.\n",size+buffer_size);
|
|
return -1;
|
|
}
|
|
if( chunk_buffer!=NULL ) {
|
|
memcpy( buffer, chunk_buffer, buffer_size );
|
|
free( chunk_buffer );
|
|
}
|
|
chunk_buffer = buffer;
|
|
buffer += buffer_size;
|
|
buffer_size += size;
|
|
|
|
if (asf_read_wrapper(fd, buffer, size, streaming_ctrl) <= 0)
|
|
return -1;
|
|
|
|
if( chunk_size2read==0 ) {
|
|
ASF_header_t *asfh = (ASF_header_t *)buffer;
|
|
if(size < (int)sizeof(ASF_header_t)) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Error: Chunk is too small.\n");
|
|
return -1;
|
|
} else mp_msg(MSGT_NETWORK,MSGL_DBG2,"Got chunk\n");
|
|
chunk_size2read = AV_RL64(&asfh->objh.size);
|
|
mp_msg(MSGT_NETWORK,MSGL_DBG2,"Size 2 read=%d\n", chunk_size2read);
|
|
}
|
|
} while( buffer_size<chunk_size2read);
|
|
buffer = chunk_buffer;
|
|
size = buffer_size;
|
|
|
|
start = sizeof(ASF_header_t);
|
|
|
|
pos = find_asf_guid(buffer, asf_file_header_guid, start, size);
|
|
if (pos >= 0) {
|
|
ASF_file_header_t *fileh = (ASF_file_header_t *) &buffer[pos];
|
|
pos += sizeof(ASF_file_header_t);
|
|
if (pos > size) goto len_err_out;
|
|
/*
|
|
if(fileh.packetsize != fileh.packetsize2) {
|
|
printf("Error packetsize check don't match\n");
|
|
return -1;
|
|
}
|
|
*/
|
|
asf_ctrl->packet_size = AV_RL32(&fileh->max_packet_size);
|
|
// before playing.
|
|
// preroll: time in ms to bufferize before playing
|
|
unsigned int preroll = (unsigned int)(((double)fileh->preroll/1000.0)*((double)fileh->max_bitrate/8.0));
|
|
// buffer in KBytes, *5 assuming the prefill is 20% of the buffer.
|
|
s->cache_size = preroll / 1024 * 5;
|
|
}
|
|
|
|
pos = start;
|
|
while ((pos = find_asf_guid(buffer, asf_stream_header_guid, pos, size)) >= 0)
|
|
{
|
|
ASF_stream_header_t *streamh = (ASF_stream_header_t *)&buffer[pos];
|
|
pos += sizeof(ASF_stream_header_t);
|
|
if (pos > size) goto len_err_out;
|
|
switch(ASF_LOAD_GUID_PREFIX(streamh->type)) {
|
|
case 0xF8699E40 : // audio stream
|
|
if(asf_ctrl->audio_streams == NULL){
|
|
asf_ctrl->audio_streams = malloc(sizeof(int));
|
|
asf_ctrl->n_audio = 1;
|
|
} else {
|
|
asf_ctrl->n_audio++;
|
|
asf_ctrl->audio_streams = realloc(asf_ctrl->audio_streams,
|
|
asf_ctrl->n_audio*sizeof(int));
|
|
}
|
|
asf_ctrl->audio_streams[asf_ctrl->n_audio-1] = AV_RL16(&streamh->stream_no);
|
|
break;
|
|
case 0xBC19EFC0 : // video stream
|
|
if(asf_ctrl->video_streams == NULL){
|
|
asf_ctrl->video_streams = malloc(sizeof(int));
|
|
asf_ctrl->n_video = 1;
|
|
} else {
|
|
asf_ctrl->n_video++;
|
|
asf_ctrl->video_streams = realloc(asf_ctrl->video_streams,
|
|
asf_ctrl->n_video*sizeof(int));
|
|
}
|
|
asf_ctrl->video_streams[asf_ctrl->n_video-1] = AV_RL16(&streamh->stream_no);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// always allocate to avoid lots of ifs later
|
|
v_rates = calloc(asf_ctrl->n_video, sizeof(int));
|
|
a_rates = calloc(asf_ctrl->n_audio, sizeof(int));
|
|
|
|
pos = find_asf_guid(buffer, asf_stream_group_guid, start, size);
|
|
if (pos >= 0) {
|
|
// stream bitrate properties object
|
|
int stream_count;
|
|
char *ptr = &buffer[pos];
|
|
char *end = &buffer[size];
|
|
|
|
mp_msg(MSGT_NETWORK, MSGL_V, "Stream bitrate properties object\n");
|
|
if (ptr + 2 > end) goto len_err_out;
|
|
stream_count = AV_RL16(ptr);
|
|
ptr += 2;
|
|
mp_msg(MSGT_NETWORK, MSGL_V, " stream count=[0x%x][%u]\n",
|
|
stream_count, stream_count );
|
|
for( i=0 ; i<stream_count ; i++ ) {
|
|
uint32_t rate;
|
|
int id;
|
|
int j;
|
|
if (ptr + 6 > end) goto len_err_out;
|
|
id = AV_RL16(ptr);
|
|
ptr += 2;
|
|
rate = AV_RL32(ptr);
|
|
ptr += 4;
|
|
mp_msg(MSGT_NETWORK, MSGL_V,
|
|
" stream id=[0x%x][%u]\n", id, id);
|
|
mp_msg(MSGT_NETWORK, MSGL_V,
|
|
" max bitrate=[0x%x][%u]\n", rate, rate);
|
|
for (j = 0; j < asf_ctrl->n_video; j++) {
|
|
if (id == asf_ctrl->video_streams[j]) {
|
|
mp_msg(MSGT_NETWORK, MSGL_V, " is video stream\n");
|
|
v_rates[j] = rate;
|
|
break;
|
|
}
|
|
}
|
|
for (j = 0; j < asf_ctrl->n_audio; j++) {
|
|
if (id == asf_ctrl->audio_streams[j]) {
|
|
mp_msg(MSGT_NETWORK, MSGL_V, " is audio stream\n");
|
|
a_rates[j] = rate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(buffer);
|
|
|
|
// automatic stream selection based on bandwidth
|
|
if (bw == 0) bw = INT_MAX;
|
|
mp_msg(MSGT_NETWORK, MSGL_V, "Max bandwidth set to %d\n", bw);
|
|
|
|
if (asf_ctrl->n_audio) {
|
|
// find lowest-bitrate audio stream
|
|
a_rate = a_rates[0];
|
|
a_idx = 0;
|
|
for (i = 0; i < asf_ctrl->n_audio; i++) {
|
|
if (a_rates[i] < a_rate) {
|
|
a_rate = a_rates[i];
|
|
a_idx = i;
|
|
}
|
|
}
|
|
if (max_idx(asf_ctrl->n_video, v_rates, bw - a_rate) < 0) {
|
|
// both audio and video are not possible, try video only next
|
|
a_idx = -1;
|
|
a_rate = 0;
|
|
}
|
|
}
|
|
// find best video stream
|
|
v_idx = max_idx(asf_ctrl->n_video, v_rates, bw - a_rate);
|
|
if (v_idx >= 0)
|
|
v_rate = v_rates[v_idx];
|
|
|
|
// find best audio stream
|
|
a_idx = max_idx(asf_ctrl->n_audio, a_rates, bw - v_rate);
|
|
|
|
free(v_rates);
|
|
free(a_rates);
|
|
|
|
if (a_idx < 0 && v_idx < 0) {
|
|
mp_tmsg(MSGT_NETWORK, MSGL_FATAL, "Bandwidth too small, file cannot be played!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (*streaming_ctrl->audio_id_ptr > 0)
|
|
// a audio stream was forced
|
|
asf_ctrl->audio_id = *streaming_ctrl->audio_id_ptr;
|
|
else if (a_idx >= 0)
|
|
asf_ctrl->audio_id = asf_ctrl->audio_streams[a_idx];
|
|
else if (asf_ctrl->n_audio) {
|
|
mp_tmsg(MSGT_NETWORK, MSGL_WARN, "Bandwidth too small, deselected audio stream.\n");
|
|
*streaming_ctrl->audio_id_ptr = -2;
|
|
}
|
|
|
|
if (*streaming_ctrl->video_id_ptr > 0)
|
|
// a video stream was forced
|
|
asf_ctrl->video_id = *streaming_ctrl->video_id_ptr;
|
|
else if (v_idx >= 0)
|
|
asf_ctrl->video_id = asf_ctrl->video_streams[v_idx];
|
|
else if (asf_ctrl->n_video) {
|
|
mp_tmsg(MSGT_NETWORK, MSGL_WARN, "Bandwidth too small, deselected video stream.\n");
|
|
*streaming_ctrl->video_id_ptr = -2;
|
|
}
|
|
|
|
return 1;
|
|
|
|
len_err_out:
|
|
mp_tmsg(MSGT_NETWORK, MSGL_FATAL, "Invalid length in ASF header!\n");
|
|
free(buffer);
|
|
free(v_rates);
|
|
free(a_rates);
|
|
return -1;
|
|
}
|
|
|
|
static int asf_http_streaming_read( int fd, char *buffer, int size, streaming_ctrl_t *streaming_ctrl ) {
|
|
static ASF_stream_chunck_t chunk;
|
|
int read,chunk_size = 0;
|
|
static int rest = 0, drop_chunk = 0, waiting = 0;
|
|
asf_http_streaming_ctrl_t *asf_http_ctrl = (asf_http_streaming_ctrl_t*)streaming_ctrl->data;
|
|
|
|
while(1) {
|
|
if (rest == 0 && waiting == 0) {
|
|
if (asf_read_wrapper(fd, &chunk, sizeof(ASF_stream_chunck_t), streaming_ctrl) <= 0)
|
|
return -1;
|
|
|
|
// Endian handling of the stream chunk
|
|
le2me_ASF_stream_chunck_t(&chunk);
|
|
chunk_size = asf_streaming( &chunk, &drop_chunk );
|
|
if(chunk_size < 0) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Error while parsing chunk header\n");
|
|
return -1;
|
|
}
|
|
chunk_size -= sizeof(ASF_stream_chunck_t);
|
|
|
|
if(chunk.type != ASF_STREAMING_HEADER && (!drop_chunk)) {
|
|
if (asf_http_ctrl->packet_size < chunk_size) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Error: chunk_size > packet_size\n");
|
|
return -1;
|
|
}
|
|
waiting = asf_http_ctrl->packet_size;
|
|
} else {
|
|
waiting = chunk_size;
|
|
}
|
|
|
|
} else if (rest){
|
|
chunk_size = rest;
|
|
rest = 0;
|
|
}
|
|
|
|
read = 0;
|
|
if ( waiting >= chunk_size) {
|
|
if (chunk_size > size){
|
|
rest = chunk_size - size;
|
|
chunk_size = size;
|
|
}
|
|
if (asf_read_wrapper(fd, buffer, chunk_size, streaming_ctrl) <= 0)
|
|
return -1;
|
|
read = chunk_size;
|
|
waiting -= read;
|
|
if (drop_chunk) continue;
|
|
}
|
|
if (rest == 0 && waiting > 0 && size-read > 0) {
|
|
int s = FFMIN(waiting,size-read);
|
|
memset(buffer+read,0,s);
|
|
waiting -= s;
|
|
read += s;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return read;
|
|
}
|
|
|
|
static int asf_http_streaming_seek( int fd, int64_t pos, streaming_ctrl_t *streaming_ctrl ) {
|
|
return -1;
|
|
// to shut up gcc warning
|
|
fd++;
|
|
pos++;
|
|
streaming_ctrl=NULL;
|
|
}
|
|
|
|
static int asf_header_check( HTTP_header_t *http_hdr ) {
|
|
ASF_obj_header_t *objh;
|
|
if( http_hdr==NULL ) return -1;
|
|
if( http_hdr->body==NULL || http_hdr->body_size<sizeof(ASF_obj_header_t) ) return -1;
|
|
|
|
objh = (ASF_obj_header_t*)http_hdr->body;
|
|
if( ASF_LOAD_GUID_PREFIX(objh->guid)==0x75B22630 ) return 0;
|
|
return -1;
|
|
}
|
|
|
|
static int asf_http_streaming_type(char *content_type, char *features, HTTP_header_t *http_hdr ) {
|
|
if( content_type==NULL ) return ASF_Unknown_e;
|
|
if( !strcasecmp(content_type, "application/octet-stream") ||
|
|
!strcasecmp(content_type, "application/vnd.ms.wms-hdr.asfv1") || // New in Corona, first request
|
|
!strcasecmp(content_type, "application/x-mms-framed") || // New in Corana, second request
|
|
!strcasecmp(content_type, "video/x-ms-wmv") ||
|
|
!strcasecmp(content_type, "video/x-ms-asf")) {
|
|
|
|
if( strstr(features, "broadcast") ) {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF Live stream\n");
|
|
return ASF_Live_e;
|
|
} else {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF Prerecorded\n");
|
|
return ASF_Prerecorded_e;
|
|
}
|
|
} else {
|
|
// Ok in a perfect world, web servers should be well configured
|
|
// so we could used mime type to know the stream type,
|
|
// but guess what? All of them are not well configured.
|
|
// So we have to check for an asf header :(, but it works :p
|
|
if( http_hdr->body_size>sizeof(ASF_obj_header_t) ) {
|
|
if( asf_header_check( http_hdr )==0 ) {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF Plain text\n");
|
|
return ASF_PlainText_e;
|
|
} else if( (!strcasecmp(content_type, "text/html")) ) {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> HTML, MPlayer is not a browser...yet!\n");
|
|
return ASF_Unknown_e;
|
|
} else {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF Redirector\n");
|
|
return ASF_Redirector_e;
|
|
}
|
|
} else {
|
|
if( (!strcasecmp(content_type, "audio/x-ms-wax")) ||
|
|
(!strcasecmp(content_type, "audio/x-ms-wma")) ||
|
|
(!strcasecmp(content_type, "video/x-ms-asf")) ||
|
|
(!strcasecmp(content_type, "video/x-ms-afs")) ||
|
|
(!strcasecmp(content_type, "video/x-ms-wmv")) ||
|
|
(!strcasecmp(content_type, "video/x-ms-wma")) ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"=====> ASF Redirector\n");
|
|
return ASF_Redirector_e;
|
|
} else if( !strcasecmp(content_type, "text/plain") ) {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF Plain text\n");
|
|
return ASF_PlainText_e;
|
|
} else {
|
|
mp_msg(MSGT_NETWORK,MSGL_V,"=====> ASF unknown content-type: %s\n", content_type );
|
|
return ASF_Unknown_e;
|
|
}
|
|
}
|
|
}
|
|
return ASF_Unknown_e;
|
|
}
|
|
|
|
static HTTP_header_t *asf_http_request(streaming_ctrl_t *streaming_ctrl) {
|
|
HTTP_header_t *http_hdr;
|
|
URL_t *url = NULL;
|
|
URL_t *server_url = NULL;
|
|
asf_http_streaming_ctrl_t *asf_http_ctrl;
|
|
char str[250];
|
|
char *ptr;
|
|
int i, enable;
|
|
|
|
int offset_hi=0, offset_lo=0, length=0;
|
|
int asf_nb_stream=0, stream_id;
|
|
|
|
// Sanity check
|
|
if( streaming_ctrl==NULL ) return NULL;
|
|
url = streaming_ctrl->url;
|
|
asf_http_ctrl = (asf_http_streaming_ctrl_t*)streaming_ctrl->data;
|
|
if( url==NULL || asf_http_ctrl==NULL ) return NULL;
|
|
|
|
// Common header for all requests.
|
|
http_hdr = http_new_header();
|
|
http_set_field( http_hdr, "Accept: */*" );
|
|
http_set_field( http_hdr, "User-Agent: NSPlayer/4.1.0.3856" );
|
|
http_add_basic_authentication( http_hdr, url->username, url->password );
|
|
|
|
// Check if we are using a proxy
|
|
if( !strcasecmp( url->protocol, "mp_http_proxy" ) ) {
|
|
server_url = url_new( (url->file)+1 );
|
|
if( server_url==NULL ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"invalid proxy URL\n");
|
|
http_free( http_hdr );
|
|
return NULL;
|
|
}
|
|
http_set_uri( http_hdr, server_url->url );
|
|
sprintf( str, "Host: %.220s:%d", server_url->hostname, server_url->port );
|
|
url_free( server_url );
|
|
} else {
|
|
http_set_uri( http_hdr, url->file );
|
|
sprintf( str, "Host: %.220s:%d", url->hostname, url->port );
|
|
}
|
|
|
|
http_set_field( http_hdr, str );
|
|
http_set_field( http_hdr, "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}" );
|
|
sprintf(str,
|
|
"Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=%u:%u,request-context=%d,max-duration=%u",
|
|
offset_hi, offset_lo, asf_http_ctrl->request, length );
|
|
http_set_field( http_hdr, str );
|
|
|
|
switch( asf_http_ctrl->streaming_type ) {
|
|
case ASF_Live_e:
|
|
case ASF_Prerecorded_e:
|
|
http_set_field( http_hdr, "Pragma: xPlayStrm=1" );
|
|
ptr = str;
|
|
ptr += sprintf( ptr, "Pragma: stream-switch-entry=");
|
|
if(asf_http_ctrl->n_audio > 0) {
|
|
for( i=0; i<asf_http_ctrl->n_audio ; i++ ) {
|
|
stream_id = asf_http_ctrl->audio_streams[i];
|
|
if(stream_id == asf_http_ctrl->audio_id) {
|
|
enable = 0;
|
|
} else {
|
|
enable = 2;
|
|
continue;
|
|
}
|
|
asf_nb_stream++;
|
|
ptr += sprintf(ptr, "ffff:%x:%d ", stream_id, enable);
|
|
}
|
|
}
|
|
if(asf_http_ctrl->n_video > 0) {
|
|
for( i=0; i<asf_http_ctrl->n_video ; i++ ) {
|
|
stream_id = asf_http_ctrl->video_streams[i];
|
|
if(stream_id == asf_http_ctrl->video_id) {
|
|
enable = 0;
|
|
} else {
|
|
enable = 2;
|
|
continue;
|
|
}
|
|
asf_nb_stream++;
|
|
ptr += sprintf(ptr, "ffff:%x:%d ", stream_id, enable);
|
|
}
|
|
}
|
|
http_set_field( http_hdr, str );
|
|
sprintf( str, "Pragma: stream-switch-count=%d", asf_nb_stream );
|
|
http_set_field( http_hdr, str );
|
|
break;
|
|
case ASF_Redirector_e:
|
|
break;
|
|
case ASF_Unknown_e:
|
|
// First request goes here.
|
|
break;
|
|
default:
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"unknown ASF stream type\n");
|
|
}
|
|
|
|
http_set_field( http_hdr, "Connection: Close" );
|
|
http_build_request( http_hdr );
|
|
|
|
return http_hdr;
|
|
}
|
|
|
|
static int asf_http_parse_response(asf_http_streaming_ctrl_t *asf_http_ctrl, HTTP_header_t *http_hdr ) {
|
|
char *content_type, *pragma;
|
|
char features[64] = "\0";
|
|
size_t len;
|
|
if( http_response_parse(http_hdr)<0 ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Failed to parse HTTP response.\n");
|
|
return -1;
|
|
}
|
|
switch( http_hdr->status_code ) {
|
|
case 200:
|
|
break;
|
|
case 401: // Authentication required
|
|
return ASF_Authenticate_e;
|
|
default:
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Server returned %d:%s\n", http_hdr->status_code, http_hdr->reason_phrase);
|
|
return -1;
|
|
}
|
|
|
|
content_type = http_get_field( http_hdr, "Content-Type");
|
|
//printf("Content-Type: [%s]\n", content_type);
|
|
|
|
pragma = http_get_field( http_hdr, "Pragma");
|
|
while( pragma!=NULL ) {
|
|
char *comma_ptr=NULL;
|
|
char *end;
|
|
//printf("Pragma: [%s]\n", pragma );
|
|
// The pragma line can get severals attributes
|
|
// separeted with a comma ','.
|
|
do {
|
|
if( !strncasecmp( pragma, "features=", 9) ) {
|
|
pragma += 9;
|
|
end = strstr( pragma, "," );
|
|
if( end==NULL ) {
|
|
len = strlen(pragma);
|
|
} else {
|
|
len = (unsigned int)(end-pragma);
|
|
}
|
|
if(len > sizeof(features) - 1) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_WARN,"ASF HTTP PARSE WARNING : Pragma %s cut from %zd bytes to %zd\n",pragma,len,sizeof(features) - 1);
|
|
len = sizeof(features) - 1;
|
|
}
|
|
strncpy( features, pragma, len );
|
|
features[len]='\0';
|
|
break;
|
|
}
|
|
comma_ptr = strstr( pragma, "," );
|
|
if( comma_ptr!=NULL ) {
|
|
pragma = comma_ptr+1;
|
|
if( pragma[0]==' ' ) pragma++;
|
|
}
|
|
} while( comma_ptr!=NULL );
|
|
pragma = http_get_next_field( http_hdr );
|
|
}
|
|
asf_http_ctrl->streaming_type = asf_http_streaming_type( content_type, features, http_hdr );
|
|
return 0;
|
|
}
|
|
|
|
static int asf_http_streaming_start( stream_t *stream, int *demuxer_type ) {
|
|
HTTP_header_t *http_hdr=NULL;
|
|
URL_t *url = stream->streaming_ctrl->url;
|
|
asf_http_streaming_ctrl_t *asf_http_ctrl;
|
|
char buffer[BUFFER_SIZE];
|
|
int i, ret;
|
|
int fd = stream->fd;
|
|
int done;
|
|
int auth_retry = 0;
|
|
|
|
asf_http_ctrl = malloc(sizeof(asf_http_streaming_ctrl_t));
|
|
if( asf_http_ctrl==NULL ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_FATAL,"Memory allocation failed.\n");
|
|
return -1;
|
|
}
|
|
asf_http_ctrl->streaming_type = ASF_Unknown_e;
|
|
asf_http_ctrl->request = 1;
|
|
asf_http_ctrl->audio_streams = asf_http_ctrl->video_streams = NULL;
|
|
asf_http_ctrl->n_audio = asf_http_ctrl->n_video = 0;
|
|
stream->streaming_ctrl->data = (void*)asf_http_ctrl;
|
|
|
|
do {
|
|
done = 1;
|
|
if( fd>0 ) closesocket( fd );
|
|
|
|
if( !strcasecmp( url->protocol, "mp_http_proxy" ) ) {
|
|
if( url->port==0 ) url->port = 8080;
|
|
} else {
|
|
if( url->port==0 ) url->port = 80;
|
|
}
|
|
fd = connect2Server( url->hostname, url->port, 1);
|
|
if( fd<0 ) return fd;
|
|
|
|
http_hdr = asf_http_request( stream->streaming_ctrl );
|
|
mp_msg(MSGT_NETWORK,MSGL_DBG2,"Request [%s]\n", http_hdr->buffer );
|
|
for(i=0; i < (int)http_hdr->buffer_size ; ) {
|
|
int r = send( fd, http_hdr->buffer+i, http_hdr->buffer_size-i, DEFAULT_SEND_FLAGS );
|
|
if(r <0) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"socket write error: %s\n",strerror(errno));
|
|
goto err_out;
|
|
}
|
|
i += r;
|
|
}
|
|
http_free( http_hdr );
|
|
http_hdr = http_new_header();
|
|
do {
|
|
i = recv( fd, buffer, BUFFER_SIZE, 0 );
|
|
//printf("read: %d\n", i );
|
|
if( i<=0 ) {
|
|
perror("read");
|
|
goto err_out;
|
|
}
|
|
http_response_append( http_hdr, buffer, i );
|
|
} while( !http_is_header_entire( http_hdr ) );
|
|
if( mp_msg_test(MSGT_NETWORK,MSGL_V) ) {
|
|
http_hdr->buffer[http_hdr->buffer_size]='\0';
|
|
mp_msg(MSGT_NETWORK,MSGL_DBG2,"Response [%s]\n", http_hdr->buffer );
|
|
}
|
|
ret = asf_http_parse_response(asf_http_ctrl, http_hdr);
|
|
if( ret<0 ) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"Failed to parse header.\n");
|
|
goto err_out;
|
|
}
|
|
switch( asf_http_ctrl->streaming_type ) {
|
|
case ASF_Live_e:
|
|
case ASF_Prerecorded_e:
|
|
case ASF_PlainText_e:
|
|
if( http_hdr->body_size>0 ) {
|
|
if( streaming_bufferize( stream->streaming_ctrl, http_hdr->body, http_hdr->body_size )<0 ) {
|
|
goto err_out;
|
|
}
|
|
}
|
|
if( asf_http_ctrl->request==1 ) {
|
|
if( asf_http_ctrl->streaming_type!=ASF_PlainText_e ) {
|
|
// First request, we only got the ASF header.
|
|
ret = asf_streaming_parse_header(stream,fd);
|
|
if(ret < 0) goto err_out;
|
|
if(asf_http_ctrl->n_audio == 0 && asf_http_ctrl->n_video == 0) {
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"No stream found.\n");
|
|
goto err_out;
|
|
}
|
|
asf_http_ctrl->request++;
|
|
done = 0;
|
|
} else {
|
|
done = 1;
|
|
}
|
|
}
|
|
break;
|
|
case ASF_Redirector_e:
|
|
if( http_hdr->body_size>0 ) {
|
|
if( streaming_bufferize( stream->streaming_ctrl, http_hdr->body, http_hdr->body_size )<0 ) {
|
|
goto err_out;
|
|
}
|
|
}
|
|
*demuxer_type = DEMUXER_TYPE_PLAYLIST;
|
|
done = 1;
|
|
break;
|
|
case ASF_Authenticate_e:
|
|
if( http_authenticate( http_hdr, url, &auth_retry)<0 ) return -1;
|
|
asf_http_ctrl->streaming_type = ASF_Unknown_e;
|
|
done = 0;
|
|
break;
|
|
case ASF_Unknown_e:
|
|
default:
|
|
mp_tmsg(MSGT_NETWORK,MSGL_ERR,"unknown ASF streaming type\n");
|
|
goto err_out;
|
|
}
|
|
// Check if we got a redirect.
|
|
} while(!done);
|
|
|
|
stream->fd = fd;
|
|
if( asf_http_ctrl->streaming_type==ASF_PlainText_e || asf_http_ctrl->streaming_type==ASF_Redirector_e ) {
|
|
stream->streaming_ctrl->streaming_read = nop_streaming_read;
|
|
stream->streaming_ctrl->streaming_seek = nop_streaming_seek;
|
|
} else {
|
|
stream->streaming_ctrl->streaming_read = asf_http_streaming_read;
|
|
stream->streaming_ctrl->streaming_seek = asf_http_streaming_seek;
|
|
stream->streaming = true;
|
|
}
|
|
stream->streaming_ctrl->status = streaming_playing_e;
|
|
stream->close = close_s;
|
|
|
|
http_free( http_hdr );
|
|
return 0;
|
|
|
|
err_out:
|
|
if (fd > 0)
|
|
closesocket(fd);
|
|
stream->fd = -1;
|
|
http_free(http_hdr);
|
|
return -1;
|
|
}
|
|
|
|
static int open_s(stream_t *stream,int mode, void* opts, int* file_format) {
|
|
stream->streaming_ctrl = streaming_ctrl_new();
|
|
if( stream->streaming_ctrl==NULL ) {
|
|
return STREAM_ERROR;
|
|
}
|
|
stream->streaming_ctrl->audio_id_ptr = &stream->opts->audio_id;
|
|
stream->streaming_ctrl->video_id_ptr = &stream->opts->video_id;
|
|
stream->streaming_ctrl->bandwidth = network_bandwidth;
|
|
stream->streaming_ctrl->url = url_new_with_proxy(stream->url);
|
|
|
|
mp_tmsg(MSGT_OPEN, MSGL_INFO, "STREAM_ASF, URL: %s\n", stream->url);
|
|
if((!strncmp(stream->url, "mp_http", 4)) && (*file_format!=DEMUXER_TYPE_ASF && *file_format!=DEMUXER_TYPE_UNKNOWN)) {
|
|
streaming_ctrl_free(stream->streaming_ctrl);
|
|
stream->streaming_ctrl = NULL;
|
|
return STREAM_UNSUPPORTED;
|
|
}
|
|
|
|
if(asf_streaming_start(stream, file_format) < 0) {
|
|
mp_tmsg(MSGT_OPEN, MSGL_ERR, "Failed, exiting.\n");
|
|
streaming_ctrl_free(stream->streaming_ctrl);
|
|
stream->streaming_ctrl = NULL;
|
|
return STREAM_UNSUPPORTED;
|
|
}
|
|
|
|
if (*file_format != DEMUXER_TYPE_PLAYLIST)
|
|
*file_format = DEMUXER_TYPE_ASF;
|
|
stream->type = STREAMTYPE_STREAM;
|
|
return STREAM_OK;
|
|
}
|
|
|
|
const stream_info_t stream_info_asf = {
|
|
"mms and mms over http streaming",
|
|
"null",
|
|
"Bertrand, Reimar Doeffinger, Albeu",
|
|
"originally based on work by Majormms (is that code still there?)",
|
|
open_s,
|
|
{"mp_mms", "mp_mmst", "mp_http", "mp_http_proxy", "mp_mmsh", "mp_mmshttp", NULL},
|
|
NULL,
|
|
0 // Urls are an option string
|
|
};
|