summaryrefslogtreecommitdiff
path: root/plugins/mms/libmms/mmsh.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mms/libmms/mmsh.c')
-rw-r--r--plugins/mms/libmms/mmsh.c1534
1 files changed, 1534 insertions, 0 deletions
diff --git a/plugins/mms/libmms/mmsh.c b/plugins/mms/libmms/mmsh.c
new file mode 100644
index 00000000..9a7b119b
--- /dev/null
+++ b/plugins/mms/libmms/mmsh.c
@@ -0,0 +1,1534 @@
+/*
+ * Copyright (C) 2002-2003 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine 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.
+ *
+ * xine 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
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: mmsh.c,v 1.16 2007/12/11 20:50:43 jwrdegoede Exp $
+ *
+ * MMS over HTTP protocol
+ * written by Thibaut Mattern
+ * based on mms.c and specs from avifile
+ * (http://avifile.sourceforge.net/asf-1.0.htm)
+ *
+ * TODO:
+ * error messages
+ * http support cleanup, find a way to share code with input_http.c (http.h|c)
+ * http proxy support
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+
+#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__)
+
+/* cheat a bit and call ourselves mms.c to keep the code in mmsio.h clean */
+#define __MMS_C__
+
+#include "bswap.h"
+#include "mmsh.h"
+#include "asfheader.h"
+#include "uri.h"
+#include "mms-common.h"
+
+/* #define USERAGENT "User-Agent: NSPlayer/7.1.0.3055\r\n" */
+#define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n"
+#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n"
+
+
+#define MMSH_PORT 80
+#define MMSH_UNKNOWN 0
+#define MMSH_SEEKABLE 1
+#define MMSH_LIVE 2
+
+#define CHUNK_HEADER_LENGTH 4
+#define EXT_HEADER_LENGTH 8
+#define CHUNK_TYPE_RESET 0x4324
+#define CHUNK_TYPE_DATA 0x4424
+#define CHUNK_TYPE_END 0x4524
+#define CHUNK_TYPE_ASF_HEADER 0x4824
+#define CHUNK_SIZE 65536 /* max chunk size */
+#define ASF_HEADER_SIZE (8192 * 2) /* max header size */
+
+#define SCRATCH_SIZE 1024
+
+#define SUCCESS 0
+#define ERROR 1
+#define EOS 2
+#define GOT_HEADER_N_DATA 3
+
+static const char* mmsh_FirstRequest =
+ "GET %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ USERAGENT
+ "Host: %s:%d\r\n"
+ "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%u,max-duration=0\r\n"
+ CLIENTGUID
+ "Connection: Close\r\n\r\n";
+
+static const char* mmsh_SeekableRequest =
+ "GET %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ USERAGENT
+ "Host: %s:%d\r\n"
+ "Pragma: no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=%u,max-duration=%u\r\n"
+ CLIENTGUID
+ "Pragma: xPlayStrm=1\r\n"
+ "Pragma: stream-switch-count=%d\r\n"
+ "Pragma: stream-switch-entry=%s\r\n" /* ffff:1:0 ffff:2:0 */
+ "Connection: Close\r\n\r\n";
+
+static const char* mmsh_LiveRequest =
+ "GET %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ USERAGENT
+ "Host: %s:%d\r\n"
+ "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
+ "Pragma: xPlayStrm=1\r\n"
+ CLIENTGUID
+ "Pragma: stream-switch-count=%d\r\n"
+ "Pragma: stream-switch-entry=%s\r\n"
+ "Connection: Close\r\n\r\n";
+
+
+#if 0
+/* Unused requests */
+static const char* mmsh_PostRequest =
+ "POST %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ USERAGENT
+ "Host: %s\r\n"
+ "Pragma: client-id=%u\r\n"
+/* "Pragma: log-line=no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=2,max-duration=%u\r\n" */
+ "Pragma: Content-Length: 0\r\n"
+ CLIENTGUID
+ "\r\n";
+
+static const char* mmsh_RangeRequest =
+ "GET %s HTTP/1.0\r\n"
+ "Accept: */*\r\n"
+ USERAGENT
+ "Host: %s:%d\r\n"
+ "Range: bytes=%Lu-\r\n"
+ CLIENTGUID
+ "Connection: Close\r\n\r\n";
+#endif
+
+
+
+/*
+ * mmsh specific types
+ */
+
+
+struct mmsh_s {
+
+ int s;
+
+ /* url parsing */
+ char *url;
+ char *proxy_url;
+ char *proto;
+ char *connect_host;
+ int connect_port;
+ char *http_host;
+ int http_port;
+ int http_request_number;
+ char *proxy_user;
+ char *proxy_password;
+ char *host_user;
+ char *host_password;
+ char *uri;
+
+ char str[SCRATCH_SIZE]; /* scratch buffer to built strings */
+
+ int stream_type; /* seekable or broadcast */
+
+ /* receive buffer */
+
+ /* chunk */
+ uint16_t chunk_type;
+ uint16_t chunk_length;
+ uint32_t chunk_seq_number;
+ uint8_t buf[CHUNK_SIZE];
+
+ int buf_size;
+ int buf_read;
+
+ uint8_t asf_header[ASF_HEADER_SIZE];
+ uint32_t asf_header_len;
+ uint32_t asf_header_read;
+ int num_stream_ids;
+ mms_stream_t streams[ASF_MAX_NUM_STREAMS];
+ uint32_t packet_length;
+ int64_t file_length;
+ uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */
+ uint64_t preroll;
+ uint64_t asf_num_packets;
+ char guid[37];
+
+ int has_audio;
+ int has_video;
+ int seekable;
+
+ off_t current_pos;
+ int user_bandwidth;
+};
+
+static int fallback_io_select(void *data, int socket, int state, int timeout_msec)
+{
+ fd_set set;
+ struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000};
+ FD_ZERO(&set);
+ FD_SET(socket, &set);
+ return select(1, (state == MMS_IO_READ_READY) ? &set : NULL,
+ (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv);
+}
+
+static off_t fallback_io_read(void *data, int socket, char *buf, off_t num)
+{
+ off_t len = 0, ret;
+/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */
+ errno = 0;
+ while (len < num)
+ {
+ ret = (off_t)read(socket, buf + len, num - len);
+ if(ret == 0)
+ break; /* EOS */
+ if(ret < 0) {
+ lprintf("mmsh: read error @ len = %lld: %s\n", (long long int) len,
+ strerror(errno));
+ switch(errno)
+ {
+ case EAGAIN:
+ continue;
+ default:
+ /* if already read something, return it, we will fail next time */
+ return len ? len : ret;
+ }
+ }
+ len += ret;
+ }
+ return len;
+}
+
+static off_t fallback_io_write(void *data, int socket, char *buf, off_t num)
+{
+ return (off_t)write(socket, buf, num);
+}
+
+static int fallback_io_tcp_connect(void *data, const char *host, int port)
+{
+
+ struct hostent *h;
+ int i, s;
+
+ h = gethostbyname(host);
+ if (h == NULL) {
+ lprintf("mmsh: unable to resolve host: %s\n", host);
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s == -1) {
+ lprintf("mmsh: failed to create socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) {
+ lprintf("mmsh: failed to set socket flags: %s\n", strerror(errno));
+ return -1;
+ }
+
+ for (i = 0; h->h_addr_list[i]; i++) {
+ struct in_addr ia;
+ struct sockaddr_in sin;
+
+ memcpy (&ia, h->h_addr_list[i], 4);
+ sin.sin_family = AF_INET;
+ sin.sin_addr = ia;
+ sin.sin_port = htons(port);
+
+ if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) {
+ continue;
+ }
+
+ return s;
+ }
+ close(s);
+ return -1;
+}
+
+
+static mms_io_t fallback_io =
+ {
+ &fallback_io_select,
+ NULL,
+ &fallback_io_read,
+ NULL,
+ &fallback_io_write,
+ NULL,
+ &fallback_io_tcp_connect,
+ NULL,
+ };
+
+static mms_io_t default_io = {
+ &fallback_io_select,
+ NULL,
+ &fallback_io_read,
+ NULL,
+ &fallback_io_write,
+ NULL,
+ &fallback_io_tcp_connect,
+ NULL,
+ };
+
+
+#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args))
+#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args))
+#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args))
+#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args))
+
+static int get_guid (unsigned char *buffer, int offset) {
+ int i;
+ GUID g;
+
+ g.Data1 = LE_32(buffer + offset);
+ g.Data2 = LE_16(buffer + offset + 4);
+ g.Data3 = LE_16(buffer + offset + 6);
+ for(i = 0; i < 8; i++) {
+ g.Data4[i] = buffer[offset + 8 + i];
+ }
+
+ for (i = 1; i < GUID_END; i++) {
+ if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) {
+ lprintf("mmsh: GUID: %s\n", guids[i].name);
+ return i;
+ }
+ }
+
+ lprintf("mmsh: unknown GUID: 0x%x, 0x%x, 0x%x, "
+ "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n",
+ g.Data1, g.Data2, g.Data3,
+ g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3],
+ g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);
+ return GUID_ERROR;
+}
+
+static int send_command (mms_io_t *io, mmsh_t *this, char *cmd) {
+ int length;
+
+ lprintf("mmsh: send_command:\n%s\n", cmd);
+
+ length = strlen(cmd);
+ if (io_write(io, this->s, cmd, length) != length) {
+ lprintf ("mmsh: send error.\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int get_answer (mms_io_t *io, mmsh_t *this) {
+
+ int done, len, linenum;
+ char *features;
+
+ done = 0; len = 0; linenum = 0;
+ this->stream_type = MMSH_UNKNOWN;
+
+ while (!done) {
+
+ if (io_read(io, this->s, &(this->buf[len]), 1) != 1) {
+ lprintf ("mmsh: alart: end of stream\n");
+ return 0;
+ }
+
+ if (this->buf[len] == '\012') {
+
+ this->buf[len] = '\0';
+ len--;
+
+ if ((len >= 0) && (this->buf[len] == '\015')) {
+ this->buf[len] = '\0';
+ len--;
+ }
+
+ linenum++;
+
+ lprintf("mmsh: answer: >%s<\n", this->buf);
+
+ if (linenum == 1) {
+ int httpver, httpsub, httpcode;
+ char httpstatus[51];
+
+ if (sscanf(this->buf, "HTTP/%d.%d %d %50[^\015\012]", &httpver, &httpsub,
+ &httpcode, httpstatus) != 4) {
+ lprintf ("mmsh: bad response format\n");
+ return 0;
+ }
+
+ if (httpcode >= 300 && httpcode < 400) {
+ lprintf ("mmsh: 3xx redirection not implemented: >%d %s<\n", httpcode, httpstatus);
+ return 0;
+ }
+
+ if (httpcode < 200 || httpcode >= 300) {
+ lprintf ("mmsh: http status not 2xx: >%d %s<\n", httpcode, httpstatus);
+ return 0;
+ }
+ } else {
+
+ if (!strncasecmp(this->buf, "Location: ", 10)) {
+ lprintf ("mmsh: Location redirection not implemented.\n");
+ return 0;
+ }
+
+ if (!strncasecmp(this->buf, "Pragma:", 7)) {
+ features = strstr(this->buf + 7, "features=");
+ if (features) {
+ if (strstr(features, "seekable")) {
+ lprintf("mmsh: seekable stream\n");
+ this->stream_type = MMSH_SEEKABLE;
+ this->seekable = 1;
+ } else {
+ if (strstr(features, "broadcast")) {
+ lprintf("mmsh: live stream\n");
+ this->stream_type = MMSH_LIVE;
+ this->seekable = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if (len == -1) {
+ done = 1;
+ } else {
+ len = 0;
+ }
+ } else {
+ len ++;
+ }
+ }
+ if (this->stream_type == MMSH_UNKNOWN) {
+ lprintf ("mmsh: unknown stream type\n");
+ this->stream_type = MMSH_SEEKABLE; /* FIXME ? */
+ this->seekable = 1;
+ }
+ return 1;
+}
+
+static int get_chunk_header (mms_io_t *io, mmsh_t *this) {
+ uint8_t chunk_header[CHUNK_HEADER_LENGTH];
+ uint8_t ext_header[EXT_HEADER_LENGTH];
+ int read_len;
+ int ext_header_len;
+
+ /* read chunk header */
+ read_len = io_read(io, this->s, chunk_header, CHUNK_HEADER_LENGTH);
+ if (read_len != CHUNK_HEADER_LENGTH) {
+ if (read_len == 0)
+ return EOS;
+ lprintf("mmsh: chunk header read failed, %d != %d\n", read_len, CHUNK_HEADER_LENGTH);
+ return ERROR;
+ }
+ this->chunk_type = LE_16 (&chunk_header[0]);
+ this->chunk_length = LE_16 (&chunk_header[2]);
+
+ switch (this->chunk_type) {
+ case CHUNK_TYPE_DATA:
+ ext_header_len = 8;
+ break;
+ case CHUNK_TYPE_END:
+ ext_header_len = 4;
+ break;
+ case CHUNK_TYPE_ASF_HEADER:
+ ext_header_len = 8;
+ break;
+ case CHUNK_TYPE_RESET:
+ ext_header_len = 4;
+ break;
+ default:
+ ext_header_len = 0;
+ }
+ /* read extended header */
+ if (ext_header_len > 0) {
+ read_len = io_read (io, this->s, ext_header, ext_header_len);
+ if (read_len != ext_header_len) {
+ lprintf("mmsh: extended header read failed. %d != %d\n", read_len, ext_header_len);
+ return ERROR;
+ }
+ }
+
+ if (this->chunk_type == CHUNK_TYPE_DATA || this->chunk_type == CHUNK_TYPE_END)
+ this->chunk_seq_number = LE_32 (&ext_header[0]);
+
+ /* display debug infos */
+#ifdef DEBUG
+ switch (this->chunk_type) {
+ case CHUNK_TYPE_DATA:
+ lprintf ("chunk type: CHUNK_TYPE_DATA\n");
+ lprintf ("chunk length: %d\n", this->chunk_length);
+ lprintf ("chunk seq: %d\n", this->chunk_seq_number);
+ lprintf ("unknown: %d\n", ext_header[4]);
+ lprintf ("mmsh seq: %d\n", ext_header[5]);
+ lprintf ("len2: %d\n", LE_16(&ext_header[6]));
+ break;
+ case CHUNK_TYPE_END:
+ lprintf ("chunk type: CHUNK_TYPE_END\n");
+ lprintf ("continue: %d\n", this->chunk_seq_number);
+ break;
+ case CHUNK_TYPE_ASF_HEADER:
+ lprintf ("chunk type: CHUNK_TYPE_ASF_HEADER\n");
+ lprintf ("chunk length: %d\n", this->chunk_length);
+ lprintf ("unknown: %2X %2X %2X %2X %2X %2X\n",
+ ext_header[0], ext_header[1], ext_header[2], ext_header[3],
+ ext_header[4], ext_header[5]);
+ lprintf ("len2: %d\n", LE_16(&ext_header[6]));
+ break;
+ case CHUNK_TYPE_RESET:
+ lprintf ("chunk type: CHUNK_TYPE_RESET\n");
+ lprintf ("chunk seq: %d\n", this->chunk_seq_number);
+ lprintf ("unknown: %2X %2X %2X %2X\n",
+ ext_header[0], ext_header[1], ext_header[2], ext_header[3]);
+ break;
+ default:
+ lprintf ("unknown chunk: %4X\n", this->chunk_type);
+ }
+#endif
+
+ this->chunk_length -= ext_header_len;
+ return SUCCESS;
+}
+
+static int get_header (mms_io_t *io, mmsh_t *this) {
+ int ret, len = 0;
+
+ this->asf_header_len = 0;
+ this->asf_header_read = 0;
+ this->buf_size = 0;
+
+ /* read chunk */
+ while (1) {
+ if ((ret = get_chunk_header(io, this)) == SUCCESS) {
+ if (this->chunk_type == CHUNK_TYPE_ASF_HEADER) {
+ if ((this->asf_header_len + this->chunk_length) > ASF_HEADER_SIZE) {
+ lprintf ("mmsh: the asf header exceed %d bytes\n", ASF_HEADER_SIZE);
+ return ERROR;
+ } else {
+ len = io_read(io, this->s, this->asf_header + this->asf_header_len,
+ this->chunk_length);
+ if (len > 0)
+ this->asf_header_len += len;
+ if (len != this->chunk_length) {
+ lprintf ("mmsh: asf header chunk read failed, %d != %d\n", len,
+ this->chunk_length);
+ return ERROR;
+ }
+ }
+ } else {
+ break;
+ }
+ } else {
+ if (this->asf_header_len == 0 || ret != EOS)
+ lprintf("mmsh: get_header failed to get chunk header\n");
+ return ret;
+ }
+ }
+
+ if (this->chunk_type == CHUNK_TYPE_DATA) {
+ /* read the first data chunk */
+ len = io_read (io, this->s, this->buf, this->chunk_length);
+
+ if (len != this->chunk_length) {
+ lprintf ("mmsh: asf data chunk read failed, %d != %d\n", len,
+ this->chunk_length);
+ return ERROR;
+ } else {
+ /* check and 0 pad the first data chunk */
+ if (this->chunk_length > this->packet_length) {
+ lprintf ("mmsh: chunk_length(%d) > packet_length(%d)\n",
+ this->chunk_length, this->packet_length);
+ return ERROR;
+ }
+
+ /* explicit padding with 0 */
+ if (this->chunk_length < this->packet_length)
+ memset(this->buf + this->chunk_length, 0,
+ this->packet_length - this->chunk_length);
+
+ this->buf_size = this->packet_length;
+
+ return SUCCESS;
+ }
+ } else {
+ /* unexpected packet type */
+ lprintf ("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type);
+ return ERROR;
+ }
+}
+
+static void interp_stream_properties(mmsh_t *this, int i) {
+ uint16_t flags;
+ uint16_t stream_id;
+ int type;
+ int encrypted;
+ int guid;
+
+ guid = get_guid(this->asf_header, i);
+ switch (guid) {
+ case GUID_ASF_AUDIO_MEDIA:
+ type = ASF_STREAM_TYPE_AUDIO;
+ this->has_audio = 1;
+ break;
+
+ case GUID_ASF_VIDEO_MEDIA:
+ case GUID_ASF_JFIF_MEDIA:
+ case GUID_ASF_DEGRADABLE_JPEG_MEDIA:
+ type = ASF_STREAM_TYPE_VIDEO;
+ this->has_video = 1;
+ break;
+
+ case GUID_ASF_COMMAND_MEDIA:
+ type = ASF_STREAM_TYPE_CONTROL;
+ break;
+
+ default:
+ type = ASF_STREAM_TYPE_UNKNOWN;
+ }
+
+ flags = LE_16(this->asf_header + i + 48);
+ stream_id = flags & 0x7F;
+ encrypted = flags >> 15;
+
+ lprintf("mmsh: stream object, stream id: %d, type: %d, encrypted: %d\n",
+ stream_id, type, encrypted);
+
+ if (this->num_stream_ids >= ASF_MAX_NUM_STREAMS) {
+ lprintf("mmsh: too many streams, skipping\n");
+ return;
+ }
+
+ this->streams[this->num_stream_ids].stream_type = type;
+ this->streams[this->num_stream_ids].stream_id = stream_id;
+ this->num_stream_ids++;
+}
+
+static void interp_header (mms_io_t *io, mmsh_t *this) {
+
+ int i;
+
+ this->packet_length = 0;
+ this->num_stream_ids = 0;
+ this->asf_num_packets = 0;
+
+ /*
+ * parse asf header
+ */
+
+ i = 30;
+ while ((i + 24) <= this->asf_header_len) {
+
+ int guid;
+ uint64_t length;
+
+ guid = get_guid(this->asf_header, i);
+ length = LE_64(this->asf_header + i + 16);
+
+ if ((i + length) > this->asf_header_len) return;
+
+ switch (guid) {
+
+ case GUID_ASF_FILE_PROPERTIES:
+
+ this->packet_length = LE_32(this->asf_header + i + 92);
+ if (this->packet_length > CHUNK_SIZE) {
+ this->packet_length = 0;
+ break;
+ }
+ this->file_length = LE_64(this->asf_header + i + 40);
+ this->time_len = LE_64(this->asf_header + i + 64);
+ //this->time_len = LE_64(this->asf_header + i + 72);
+ this->preroll = LE_64(this->asf_header + i + 80);
+ lprintf("mmsh: file object, packet length = %d (%d)\n",
+ this->packet_length, LE_32(this->asf_header + i + 96));
+ break;
+
+ case GUID_ASF_STREAM_PROPERTIES:
+ interp_stream_properties(this, i + 24);
+ break;
+
+ case GUID_ASF_STREAM_BITRATE_PROPERTIES:
+ {
+ uint16_t streams = LE_16(this->asf_header + i + 24);
+ uint16_t stream_id;
+ int j, stream_index;
+
+ for(j = 0; j < streams; j++) {
+ stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6);
+
+ for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) {
+ if (this->streams[stream_index].stream_id == stream_id)
+ break;
+ }
+ if (stream_index < this->num_stream_ids) {
+ this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6);
+ this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6;
+ lprintf ("mmsh: stream id %d, bitrate %d\n", stream_id,
+ this->streams[stream_index].bitrate);
+ } else
+ lprintf ("mmsh: unknown stream id %d in bitrate properties\n",
+ stream_id);
+ }
+ }
+ break;
+
+ case GUID_ASF_HEADER_EXTENSION:
+ {
+ if ((24 + 18 + 4) > length)
+ break;
+
+ int size = LE_32(this->asf_header + i + 24 + 18);
+ int j = 24 + 18 + 4;
+ int l;
+ lprintf("mmsh: Extension header data size: %d\n", size);
+
+ while ((j + 24) <= length) {
+ guid = get_guid(this->asf_header, i + j);
+ l = LE_64(this->asf_header + i + j + 16);
+
+ if ((j + l) > length)
+ break;
+
+ if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES &&
+ (24 + 64) <= l) {
+ int stream_no = LE_16(this->asf_header + i + j + 24 + 48);
+ int name_count = LE_16(this->asf_header + i + j + 24 + 60);
+ int ext_count = LE_16(this->asf_header + i + j + 24 + 62);
+ int ext_j = 24 + 64;
+ int x;
+
+ lprintf("mmsh: l: %d\n", l);
+ lprintf("mmsh: Stream No: %d\n", stream_no);
+ lprintf("mmsh: ext_count: %d\n", ext_count);
+
+ // Loop through the number of stream names
+ for (x = 0; x < name_count && (ext_j + 4) <= l; x++) {
+ int lang_id_index;
+ int stream_name_len;
+
+ lang_id_index = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += 2;
+
+ stream_name_len = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += stream_name_len + 2;
+
+ lprintf("mmsh: Language id index: %d\n", lang_id_index);
+ lprintf("mmsh: Stream name Len: %d\n", stream_name_len);
+ }
+
+ // Loop through the number of extension system info
+ for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) {
+ ext_j += 18;
+ int len = LE_16(this->asf_header + i + j + ext_j);
+ ext_j += 4 + len;
+ }
+
+ lprintf("mmsh: ext_j: %d\n", ext_j);
+ // Finally, we arrive at the interesting point: The optional Stream Property Object
+ if ((ext_j + 24) <= l) {
+ guid = get_guid(this->asf_header, i + j + ext_j);
+ int len = LE_64(this->asf_header + i + j + ext_j + 16);
+ if (guid == GUID_ASF_STREAM_PROPERTIES &&
+ (ext_j + len) <= l) {
+ interp_stream_properties(this, i + j + ext_j + 24);
+ }
+ } else {
+ lprintf("mmsh: Sorry, field not long enough\n");
+ }
+ }
+ j += l;
+ }
+ }
+ break;
+
+ case GUID_ASF_DATA:
+ this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24);
+ lprintf("mmsh: num_packets: %d\n", (int)this->asf_num_packets);
+ break;
+ }
+
+ lprintf("mmsh: length: %llu\n", (unsigned long long)length);
+ i += length;
+ }
+}
+
+const static char *const mmsh_proto_s[] = { "mms", "mmsh", NULL };
+
+static int mmsh_valid_proto (char *proto) {
+ int i = 0;
+
+ if (!proto)
+ return 0;
+
+ while(mmsh_proto_s[i]) {
+ if (!strcasecmp(proto, mmsh_proto_s[i])) {
+ return 1;
+ }
+ i++;
+ }
+ return 0;
+}
+
+/*
+ * returns 1 on error
+ */
+static int mmsh_tcp_connect(mms_io_t *io, mmsh_t *this) {
+ if (!this->connect_port) this->connect_port = MMSH_PORT;
+
+ /*
+ * try to connect
+ */
+ lprintf("mmsh: try to connect to %s on port %d \n", this->connect_host, this->connect_port);
+
+ this->s = io_connect (io, this->connect_host, this->connect_port);
+
+ if (this->s == -1) {
+ lprintf("mmsh: failed to connect '%s'\n", this->connect_host);
+ return 1;
+ }
+
+ lprintf("mmsh: connected\n");
+
+ return 0;
+}
+
+static int mmsh_connect_int (mms_io_t *io, mmsh_t *this, off_t seek, uint32_t time_seek) {
+ int i;
+ int video_stream = -1;
+ int audio_stream = -1;
+ int max_arate = -1;
+ int min_vrate = -1;
+ int min_bw_left = 0;
+ int bandwitdh_left;
+ char stream_selection[10 * ASF_MAX_NUM_STREAMS]; /* 10 chars per stream */
+ int offset;
+
+ /* Close exisiting connection (if any) and connect */
+ if (this->s != -1)
+ close(this->s);
+
+ if (mmsh_tcp_connect(io, this)) {
+ return 0;
+ }
+
+ /*
+ * let the negotiations begin...
+ */
+ this->num_stream_ids = 0;
+
+ /* first request */
+ lprintf("mmsh: first http request\n");
+
+ snprintf (this->str, SCRATCH_SIZE, mmsh_FirstRequest, this->uri,
+ this->http_host, this->http_port, this->http_request_number++);
+
+ if (!send_command (io, this, this->str))
+ goto fail;
+
+ if (!get_answer (io, this))
+ goto fail;
+
+ /* Don't check for != SUCCESS as EOS is normal here too */
+ if (get_header(io, this) == ERROR)
+ goto fail;
+
+ interp_header(io, this);
+ if (!this->packet_length || !this->num_stream_ids)
+ goto fail;
+
+ close(this->s);
+
+ /* choose the best quality for the audio stream */
+ /* i've never seen more than one audio stream */
+ for (i = 0; i < this->num_stream_ids; i++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_AUDIO:
+ if ((audio_stream == -1) || (this->streams[i].bitrate > max_arate)) {
+ audio_stream = this->streams[i].stream_id;
+ max_arate = this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* choose a video stream adapted to the user bandwidth */
+ bandwitdh_left = this->user_bandwidth - max_arate;
+ if (bandwitdh_left < 0) {
+ bandwitdh_left = 0;
+ }
+ lprintf("mmsh: bandwitdh %d, left %d\n", this->user_bandwidth, bandwitdh_left);
+
+ min_bw_left = bandwitdh_left;
+ for (i = 0; i < this->num_stream_ids; i++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_VIDEO:
+ if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) &&
+ (bandwitdh_left >= this->streams[i].bitrate)) {
+ video_stream = this->streams[i].stream_id;
+ min_bw_left = bandwitdh_left - this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* choose the stream with the lower bitrate */
+ if ((video_stream == -1) && this->has_video) {
+ for (i = 0; i < this->num_stream_ids; i++) {
+ switch (this->streams[i].stream_type) {
+ case ASF_STREAM_TYPE_VIDEO:
+ if ((video_stream == -1) ||
+ (this->streams[i].bitrate < min_vrate) ||
+ (!min_vrate)) {
+ video_stream = this->streams[i].stream_id;
+ min_vrate = this->streams[i].bitrate;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ lprintf("mmsh: audio stream %d, video stream %d\n", audio_stream, video_stream);
+
+ /* second request */
+ lprintf("mmsh: second http request\n");
+
+ if (mmsh_tcp_connect(io, this)) {
+ return 0;
+ }
+
+ /* stream selection string */
+ /* The same selection is done with mmst */
+ /* 0 means selected */
+ /* 2 means disabled */
+ offset = 0;
+ for (i = 0; i < this->num_stream_ids; i++) {
+ int size;
+ if ((this->streams[i].stream_id == audio_stream) ||
+ (this->streams[i].stream_id == video_stream)) {
+ size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset,
+ "ffff:%d:0 ", this->streams[i].stream_id);
+ } else {
+ lprintf("mmsh: disabling stream %d\n", this->streams[i].stream_id);
+ size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset,
+ "ffff:%d:2 ", this->streams[i].stream_id);
+ }
+ if (size < 0) goto fail;
+ offset += size;
+ }
+
+ switch (this->stream_type) {
+ case MMSH_SEEKABLE:
+ snprintf (this->str, SCRATCH_SIZE, mmsh_SeekableRequest, this->uri,
+ this->http_host, this->http_port, time_seek,
+ (unsigned int)(seek >> 32),
+ (unsigned int)seek, this->http_request_number++, 0,
+ this->num_stream_ids, stream_selection);
+ break;
+ case MMSH_LIVE:
+ snprintf (this->str, SCRATCH_SIZE, mmsh_LiveRequest, this->uri,
+ this->http_host, this->http_port, this->http_request_number++,
+ this->num_stream_ids, stream_selection);
+ break;
+ }
+
+ if (!send_command (io, this, this->str))
+ goto fail;
+
+ if (!get_answer (io, this))
+ goto fail;
+
+ if (get_header(io, this) != SUCCESS)
+ goto fail;
+
+ interp_header(io, this);
+ if (!this->packet_length || !this->num_stream_ids)
+ goto fail;
+
+ for (i = 0; i < this->num_stream_ids; i++) {
+ if ((this->streams[i].stream_id != audio_stream) &&
+ (this->streams[i].stream_id != video_stream)) {
+ lprintf("disabling stream %d\n", this->streams[i].stream_id);
+
+ /* forces the asf demuxer to not choose this stream */
+ if (this->streams[i].bitrate_pos) {
+ if (this->streams[i].bitrate_pos + 3 < ASF_HEADER_SIZE) {
+ this->asf_header[this->streams[i].bitrate_pos] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 1] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 2] = 0;
+ this->asf_header[this->streams[i].bitrate_pos + 3] = 0;
+ } else {
+ lprintf("mmsh: attempt to write beyond asf header limit");
+ }
+ }
+ }
+ }
+ return 1;
+fail:
+ close(this->s);
+ this->s = -1;
+ return 0;
+}
+
+mmsh_t *mmsh_connect (mms_io_t *io, void *data, const char *url, int bandwidth) {
+ mmsh_t *this;
+ GURI *uri = NULL;
+ GURI *proxy_uri = NULL;
+ char *proxy_env;
+ if (!url)
+ return NULL;
+
+ /*
+ * initializatoin is essential here. the fail: label depends
+ * on the various char * in our this structure to be
+ * NULL if they haven't been assigned yet.
+ */
+ this = (mmsh_t*) malloc (sizeof (mmsh_t));
+ this->url=NULL;
+ this->proxy_url = NULL;
+ this->proto = NULL;
+ this->connect_host = NULL;
+ this->http_host = NULL;
+ this->proxy_user = NULL;
+ this->proxy_password = NULL;
+ this->host_user = NULL;
+ this->host_password = NULL;
+ this->uri = NULL;
+
+ this->url = strdup(url);
+ if ((proxy_env = getenv("http_proxy")) != NULL)
+ this->proxy_url = strdup(proxy_env);
+ else
+ this->proxy_url = NULL;
+ this->s = -1;
+ this->asf_header_len = 0;
+ this->asf_header_read = 0;
+ this->num_stream_ids = 0;
+ this->packet_length = 0;
+ this->buf_size = 0;
+ this->buf_read = 0;
+ this->has_audio = 0;
+ this->has_video = 0;
+ this->current_pos = 0;
+ this->user_bandwidth = bandwidth;
+ this->http_request_number = 1;
+
+ if (this->proxy_url) {
+ proxy_uri = gnet_uri_new(this->proxy_url);
+ if (!proxy_uri) {
+ lprintf("mmsh: invalid proxy url\n");
+ goto fail;
+ }
+ if (! proxy_uri->port ) {
+ proxy_uri->port = 3128; //default squid port
+ }
+ }
+ uri = gnet_uri_new(this->url);
+ if (!uri) {
+ lprintf("mmsh: invalid url\n");
+ goto fail;
+ }
+ if (! uri->port ) {
+ //checked in tcp_connect, but it's better to initialize it here
+ uri->port = MMSH_PORT;
+ }
+ if (this->proxy_url) {
+ this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL;
+ this->connect_host = (proxy_uri->hostname) ? strdup(proxy_uri->hostname) : NULL;
+ this->connect_port = proxy_uri->port;
+ this->http_host = (uri->scheme) ? strdup(uri->hostname) : NULL;
+ this->http_port = uri->port;
+ this->proxy_user = (proxy_uri->user) ? strdup(proxy_uri->user) : NULL;
+ this->proxy_password = (proxy_uri->passwd) ? strdup(proxy_uri->passwd) : NULL;
+ this->host_user = (uri->user) ? strdup(uri->user) : NULL;
+ this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL;
+ gnet_uri_set_scheme(uri,"http");
+ this->uri = gnet_mms_helper(uri, 1);
+ } else {
+ this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL;
+ this->connect_host = (uri->hostname) ? strdup(uri->hostname) : NULL;
+ this->connect_port = uri->port;
+ this->http_host = (uri->hostname) ? strdup(uri->hostname) : NULL;
+ this->http_port = uri->port;
+ this->proxy_user = NULL;
+ this->proxy_password = NULL;
+ this->host_user =(uri->user) ? strdup(uri->user) : NULL;
+ this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL;
+ this->uri = gnet_mms_helper(uri, 1);
+ }
+
+ if(!this->uri)
+ goto fail;
+
+ if (proxy_uri) {
+ gnet_uri_delete(proxy_uri);
+ proxy_uri = NULL;
+ }
+ if (uri) {
+ gnet_uri_delete(uri);
+ uri = NULL;
+ }
+ if (!mmsh_valid_proto(this->proto)) {
+ lprintf("mmsh: unsupported protocol\n");
+ goto fail;
+ }
+
+ if (!mmsh_connect_int(io, this, 0, 0))
+ goto fail;
+
+ return this;
+
+fail:
+ lprintf("mmsh: connect failed\n");
+ if (proxy_uri)
+ gnet_uri_delete(proxy_uri);
+ if (uri)
+ gnet_uri_delete(uri);
+ if (this->s != -1)
+ close(this->s);
+ if (this->url)
+ free(this->url);
+ if (this->proxy_url)
+ free(this->proxy_url);
+ if (this->proto)
+ free(this->proto);
+ if (this->connect_host)
+ free(this->connect_host);
+ if (this->http_host)
+ free(this->http_host);
+ if (this->proxy_user)
+ free(this->proxy_user);
+ if (this->proxy_password)
+ free(this->proxy_password);
+ if (this->host_user)
+ free(this->host_user);
+ if (this->host_password)
+ free(this->host_password);
+ if (this->uri)
+ free(this->uri);
+
+ free(this);
+ return NULL;
+}
+
+static int get_media_packet (mms_io_t *io, mmsh_t *this) {
+ int ret, len = 0;
+
+ if (get_chunk_header(io, this) == SUCCESS) {
+ switch (this->chunk_type) {
+ case CHUNK_TYPE_END:
+ /* this->chunk_seq_number:
+ * 0: stop
+ * 1: a new stream follows
+ */
+ if (this->chunk_seq_number == 0)
+ return EOS;
+
+ this->http_request_number = 1;
+ if (!mmsh_connect_int (io, this, 0, 0))
+ return ERROR;
+
+ /* What todo with: current_pos ??
+ Also our chunk_seq_numbers will probably restart from 0!
+ If this happens with a seekable stream (does it ever?)
+ and we get a seek request after this were fscked! */
+ this->seekable = 0;
+
+ /* mmsh_connect_int reads the first data packet */
+ /* this->buf_size is set by mmsh_connect_int */
+ return GOT_HEADER_N_DATA;
+
+ case CHUNK_TYPE_DATA:
+ /* nothing to do */
+ break;
+
+ case CHUNK_TYPE_RESET:
+ /* next chunk is an ASF header */
+
+ if (this->chunk_length != 0) {
+ /* that's strange, don't know what to do */
+ lprintf("mmsh: non 0 sized reset chunk");
+ return ERROR;
+ }
+ if ((ret = get_header (io, this)) != SUCCESS) {
+ lprintf("mmsh: failed to get header after reset chunk\n");
+ return ret;
+ }
+ interp_header(io, this);
+
+ /* What todo with: current_pos ??
+ Also our chunk_seq_numbers might restart from 0!
+ If this happens with a seekable stream (does it ever?)
+ and we get a seek request after this were fscked! */
+ this->seekable = 0;
+
+ /* get_header reads the first data packet */
+ /* this->buf_size is set by get_header */
+ return GOT_HEADER_N_DATA;
+
+ default:
+ lprintf("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type);
+ return ERROR;
+ }
+
+ len = io_read (io, this->s, this->buf, this->chunk_length);
+
+ if (len == this->chunk_length) {
+ /* explicit padding with 0 */
+ if (this->chunk_length > this->packet_length) {
+ lprintf("mmsh: chunk_length(%d) > packet_length(%d)\n",
+ this->chunk_length, this->packet_length);
+ return ERROR;
+ }
+
+ memset(this->buf + this->chunk_length, 0,
+ this->packet_length - this->chunk_length);
+ this->buf_size = this->packet_length;
+
+ return SUCCESS;
+ } else {
+ lprintf("mmsh: media packet read error, %d != %d\n", len,
+ this->chunk_length);
+ return ERROR;
+ }
+ } else if (ret == EOS) {
+ return EOS;
+ } else {
+ lprintf("mmsh: get_media_packet failed to get chunk header\n");
+ return ret;
+ }
+}
+
+int mmsh_peek_header (mmsh_t *this, char *data, int maxsize) {
+ int len;
+
+ len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize;
+
+ memcpy(data, this->asf_header, len);
+ return len;
+}
+
+int mmsh_read (mms_io_t *io, mmsh_t *this, char *data, int len) {
+ int total;
+
+ total = 0;
+
+ /* Check if the stream didn't get closed because of previous errors */
+ if (this->s == -1)
+ return total;
+
+ while (total < len) {
+
+ if (this->asf_header_read < this->asf_header_len) {
+ int n, bytes_left ;
+
+ bytes_left = this->asf_header_len - this->asf_header_read ;
+
+ if ((len-total) < bytes_left)
+ n = len-total;
+ else
+ n = bytes_left;
+
+ memcpy (&data[total], &this->asf_header[this->asf_header_read], n);
+
+ this->asf_header_read += n;
+ total += n;
+ this->current_pos += n;
+ } else {
+
+ int n, bytes_left ;
+
+ bytes_left = this->buf_size - this->buf_read;
+
+ if (bytes_left == 0) {
+ int ret;
+
+ this->buf_size=this ->buf_read = 0;
+ ret = get_media_packet (io, this);
+
+ switch (ret) {
+ case SUCCESS:
+ break;
+ case ERROR:
+ lprintf ("mmsh: get_media_packet failed\n");
+ return total;
+ case EOS:
+ return total;
+ case GOT_HEADER_N_DATA:
+ continue;
+ }
+ bytes_left = this->buf_size;
+ }
+
+ if ((len-total) < bytes_left)
+ n = len-total;
+ else
+ n = bytes_left;
+
+ memcpy (&data[total], &this->buf[this->buf_read], n);
+
+ this->buf_read += n;
+ total += n;
+ this->current_pos += n;
+ }
+ }
+ return total;
+}
+
+off_t mmsh_seek (mms_io_t *io, mmsh_t *this, off_t offset, int origin) {
+ off_t dest;
+ off_t dest_packet_seq;
+ uint32_t orig_asf_header_len = this->asf_header_len;
+ uint32_t orig_asf_packet_len = this->packet_length;
+
+ if (!this->seekable)
+ return this->current_pos;
+
+ switch (origin) {
+ case SEEK_SET:
+ dest = offset;
+ break;
+ case SEEK_CUR:
+ dest = this->current_pos + offset;
+ break;
+ case SEEK_END:
+ dest = mmsh_get_length (this) + offset;
+ default:
+ return this->current_pos;
+ }
+
+ dest_packet_seq = dest - this->asf_header_len;
+ dest_packet_seq = dest_packet_seq >= 0 ?
+ dest_packet_seq / this->packet_length : -1;
+
+ if (dest_packet_seq < 0) {
+ if (this->chunk_seq_number > 0) {
+ lprintf("mmsh: seek within header, already read beyond first packet, resetting connection\n");
+ if (!mmsh_connect_int(io, this, 0, 0)) {
+ /* Oops no more connection let our caller know things are fscked up */
+ return this->current_pos = -1;
+ }
+ /* Some what simple / naive check to check for changed headers
+ if the header was changed we are once more fscked up */
+ if (this->asf_header_len != orig_asf_header_len ||
+ this->packet_length != orig_asf_packet_len) {
+ lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n");
+ /* Its a different stream, so its useless! */
+ close (this->s);
+ this->s = -1;
+ return this->current_pos = -1;
+ }
+ } else
+ lprintf("mmsh: seek within header, resetting buf_read\n");
+
+ // reset buf_read
+ this->buf_read = 0;
+ this->asf_header_read = dest;
+ return this->current_pos = dest;
+ }
+
+ // dest_packet_seq >= 0
+ if (this->asf_num_packets && dest == this->asf_header_len +
+ this->asf_num_packets*this->packet_length) {
+ // Requesting the packet beyond the last packet, can cause the server to
+ // not return any packet or any eos command. This can cause
+ // mms_packet_seek() to hang.
+ // This is to allow seeking at end of stream, and avoid hanging.
+ --dest_packet_seq;
+ lprintf("mmsh: seek to eos!\n");
+ }
+
+ if (dest_packet_seq != this->chunk_seq_number) {
+
+ if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) {
+ // Do not seek beyond the last packet.
+ return this->current_pos;
+ }
+
+ lprintf("mmsh: seek to %d, packet: %d\n", (int)dest, (int)dest_packet_seq);
+ if (!mmsh_connect_int(io, this, (dest_packet_seq+1) * this->packet_length, 0)) {
+ /* Oops no more connection let our caller know things are fscked up */
+ return this->current_pos = -1;
+ }
+ /* Some what simple / naive check to check for changed headers
+ if the header was changed we are once more fscked up */
+ if (this->asf_header_len != orig_asf_header_len ||
+ this->packet_length != orig_asf_packet_len) {
+ lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n");
+ /* Its a different stream, so its useless! */
+ close (this->s);
+ this->s = -1;
+ return this->current_pos = -1;
+ }
+ }
+ else
+ lprintf("mmsh: seek within current packet, dest: %d, current pos: %d\n",
+ (int)dest, (int)this->current_pos);
+
+ /* make sure asf_header is seen as fully read by mmsh_read() this is needed
+ in case our caller tries to seek over part of the header, or when we've
+ done an actual packet seek as get_header() resets asf_header_read then. */
+ this->asf_header_read = this->asf_header_len;
+
+ /* check we got what we want */
+ if (dest_packet_seq == this->chunk_seq_number) {
+ this->buf_read = dest -
+ (this->asf_header_len + dest_packet_seq*this->packet_length);
+ this->current_pos = dest;
+ } else {
+ lprintf("mmsh: Seek failed, wanted packet: %d, got packet: %d\n",
+ (int)dest_packet_seq, (int)this->chunk_seq_number);
+ this->buf_read = 0;
+ this->current_pos = this->asf_header_len + this->chunk_seq_number *
+ this->packet_length;
+ }
+
+ lprintf("mmsh: current_pos after seek to %d: %d (buf_read %d)\n",
+ (int)dest, (int)this->current_pos, (int)this->buf_read);
+
+ return this->current_pos;
+}
+
+int mmsh_time_seek (mms_io_t *io, mmsh_t *this, double time_sec) {
+ uint32_t orig_asf_header_len = this->asf_header_len;
+ uint32_t orig_asf_packet_len = this->packet_length;
+
+ if (!this->seekable)
+ return 0;
+
+ lprintf("mmsh: time seek to %f secs\n", time_sec);
+ if (!mmsh_connect_int(io, this, 0, time_sec * 1000 + this->preroll)) {
+ /* Oops no more connection let our caller know things are fscked up */
+ this->current_pos = -1;
+ return 0;
+ }
+ /* Some what simple / naive check to check for changed headers
+ if the header was changed we are once more fscked up */
+ if (this->asf_header_len != orig_asf_header_len ||
+ this->packet_length != orig_asf_packet_len) {
+ lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n");
+ /* Its a different stream, so its useless! */
+ close (this->s);
+ this->s = -1;
+ this->current_pos = -1;
+ return 0;
+ }
+
+ this->asf_header_read = this->asf_header_len;
+ this->buf_read = 0;
+ this->current_pos = this->asf_header_len + this->chunk_seq_number *
+ this->packet_length;
+
+ lprintf("mmsh, current_pos after time_seek:%d\n", (int)this->current_pos);
+
+ return 1;
+}
+
+void mmsh_close (mmsh_t *this) {
+ if (this->s != -1)
+ close(this->s);
+ if (this->url)
+ free(this->url);
+ if (this->proxy_url)
+ free(this->proxy_url);
+ if (this->proto)
+ free(this->proto);
+ if (this->connect_host)
+ free(this->connect_host);
+ if (this->http_host)
+ free(this->http_host);
+ if (this->proxy_user)
+ free(this->proxy_user);
+ if (this->proxy_password)
+ free(this->proxy_password);
+ if (this->host_user)
+ free(this->host_user);
+ if (this->host_password)
+ free(this->host_password);
+ if (this->uri)
+ free(this->uri);
+ if (this)
+ free (this);
+}
+
+
+uint32_t mmsh_get_length (mmsh_t *this) {
+ /* we could / should return this->file_len here, but usually this->file_len
+ is longer then the calculation below, as usually an asf file contains an
+ asf index object after the data stream. However since we do not have a
+ (known) way to get to this index object through mms, we return a
+ calculated size of what we can get to when we know. */
+ if (this->asf_num_packets)
+ return this->asf_header_len + this->asf_num_packets*this->packet_length;
+ else
+ return this->file_length;
+}
+
+double mmsh_get_time_length (mmsh_t *this) {
+ return (double)(this->time_len) / 1e7;
+}
+
+uint64_t mmsh_get_raw_time_length (mmsh_t *this) {
+ return this->time_len;
+}
+
+off_t mmsh_get_current_pos (mmsh_t *this) {
+ return this->current_pos;
+}
+
+uint32_t mmsh_get_asf_header_len (mmsh_t *this) {
+ return this->asf_header_len;
+}
+
+uint32_t mmsh_get_asf_packet_len (mmsh_t *this) {
+ return this->packet_length;
+}
+
+int mmsh_get_seekable (mmsh_t *this) {
+ return this->seekable;
+}