summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorGravatar waker <wakeroid@gmail.com>2010-10-31 18:25:37 +0100
committerGravatar waker <wakeroid@gmail.com>2010-10-31 18:25:37 +0100
commit86c756deeb6effd9b29968a3649f8387c0b6d62a (patch)
tree1911e2d105628de793f6c913c60f052b1f4e0a5a /plugins
parent1165a25a0ac57d3bcaf29afc9a5537523d4b0a99 (diff)
parent6e99c2322741baf2bbe50c69c29a0ffe4f2c8d0c (diff)
Merge branch 'master' into static
Conflicts: configure.ac
Diffstat (limited to 'plugins')
-rw-r--r--plugins/aac/aac.c10
-rw-r--r--plugins/aac/mp4ff/mp4ff.c5
-rw-r--r--plugins/cdda/cdda.c16
-rw-r--r--plugins/gtkui/callbacks.c4
-rw-r--r--plugins/gtkui/ddbtabstrip.c107
-rw-r--r--plugins/gtkui/deadbeef.glade74
-rw-r--r--plugins/gtkui/gtkui.c38
-rw-r--r--plugins/gtkui/interface.c39
-rw-r--r--plugins/gtkui/prefwin.c2
-rw-r--r--plugins/lastfm/lastfm.c1
-rw-r--r--plugins/mms/Makefile.am15
-rw-r--r--plugins/mms/libmms/AUTHORS6
-rw-r--r--plugins/mms/libmms/COPYING.LIB510
-rw-r--r--plugins/mms/libmms/README57
-rw-r--r--plugins/mms/libmms/README.LICENSE6
-rw-r--r--plugins/mms/libmms/asfheader.h265
-rw-r--r--plugins/mms/libmms/bswap.h282
-rw-r--r--plugins/mms/libmms/mms-common.h35
-rw-r--r--plugins/mms/libmms/mms.c1802
-rw-r--r--plugins/mms/libmms/mms.h80
-rw-r--r--plugins/mms/libmms/mmsh.c1534
-rw-r--r--plugins/mms/libmms/mmsh.h63
-rw-r--r--plugins/mms/libmms/mmsio.h93
-rw-r--r--plugins/mms/libmms/mmsx.c168
-rw-r--r--plugins/mms/libmms/mmsx.h69
-rw-r--r--plugins/mms/libmms/uri.c1033
-rw-r--r--plugins/mms/libmms/uri.h92
-rw-r--r--plugins/mms/mmsplug.c (renamed from plugins/mms/mms.c)0
-rw-r--r--plugins/mpgmad/mpgmad.c192
-rw-r--r--plugins/oss/oss.c2
-rw-r--r--plugins/tta/ttaplug.c2
-rw-r--r--plugins/vfs_curl/vfs_curl.c11
32 files changed, 6281 insertions, 332 deletions
diff --git a/plugins/aac/aac.c b/plugins/aac/aac.c
index 2f6b518b..f7925b82 100644
--- a/plugins/aac/aac.c
+++ b/plugins/aac/aac.c
@@ -726,13 +726,17 @@ aac_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) {
sampleDuration, MP4_MSECS_TIME_SCALE);
#endif
if (info->mp4sample >= info->mp4samples) {
+ if (buffer) {
+ free (buffer);
+ }
break;
}
info->mp4sample++;
samples = NeAACDecDecode(info->dec, &frame_info, buffer, buffer_size);
-#ifdef USE_MP4FF
- free (buffer);
-#endif
+
+ if (buffer) {
+ free (buffer);
+ }
if (!samples) {
break;
}
diff --git a/plugins/aac/mp4ff/mp4ff.c b/plugins/aac/mp4ff/mp4ff.c
index 9181ace9..ce33aad5 100644
--- a/plugins/aac/mp4ff/mp4ff.c
+++ b/plugins/aac/mp4ff/mp4ff.c
@@ -30,6 +30,7 @@
#include <stdlib.h>
#include <string.h>
+#include <stdio.h>
#include "mp4ffint.h"
mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f)
@@ -426,6 +427,10 @@ int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample,
if (*bytes==0) return 0;
*audio_buffer = (uint8_t*)malloc(*bytes);
+ if (!(*audio_buffer)) {
+ fprintf (stderr, "mp4ff_read_sample: malloc failure (tried to alloc %d bytes). possible mp4ff bug or memleak! please report a bug to deadbeef developers (i'm serious).\n", *bytes);
+ return 0;
+ }
mp4ff_set_sample_position(f, track, sample);
diff --git a/plugins/cdda/cdda.c b/plugins/cdda/cdda.c
index f2e5066c..2ed54b51 100644
--- a/plugins/cdda/cdda.c
+++ b/plugins/cdda/cdda.c
@@ -362,6 +362,7 @@ cddb_thread (void *items_i)
deadbeef->mutex_unlock (mutex);
cleanup_thread_params (params);
cddb_tid = 0;
+ deadbeef->plug_trigger_event_playlistchanged ();
}
static void
@@ -384,15 +385,19 @@ read_track_cdtext (CdIo_t *cdio, int track_nr, DB_playItem_t *item)
{
switch (field_type)
{
- case CDTEXT_TITLE: album = strdup (text); break;
- case CDTEXT_PERFORMER: artist = strdup (text); break;
+ case CDTEXT_TITLE: album = text; break;
+ case CDTEXT_PERFORMER: artist = text; break;
}
}
}
trace ("artist: %s; album: %s\n", artist, album);
- deadbeef->pl_replace_meta (item, "artist", artist);
- deadbeef->pl_replace_meta (item, "album", album);
+ if (artist) {
+ deadbeef->pl_replace_meta (item, "artist", artist);
+ }
+ if (album) {
+ deadbeef->pl_replace_meta (item, "album", album);
+ }
cdtext = cdio_get_cdtext (cdio, track_nr);
if (!cdtext)
@@ -414,7 +419,7 @@ read_track_cdtext (CdIo_t *cdio, int track_nr, DB_playItem_t *item)
case CDTEXT_MESSAGE: field = "comment"; break;
default: field = NULL;
}
- if (field)
+ if (field && text)
{
trace ("%s: %s\n", field, text);
deadbeef->pl_replace_meta (item, field, text);
@@ -521,7 +526,6 @@ cda_insert (DB_playItem_t *after, const char *fname) {
}
cdio_destroy (cdio);
}
- deadbeef->plug_trigger_event_playlistchanged ();
return res;
}
diff --git a/plugins/gtkui/callbacks.c b/plugins/gtkui/callbacks.c
index 7ca76d0c..84a585ee 100644
--- a/plugins/gtkui/callbacks.c
+++ b/plugins/gtkui/callbacks.c
@@ -312,7 +312,7 @@ on_mainwin_key_press_event (GtkWidget *widget,
gpointer user_data)
{
uint32_t maskedstate = (event->state &~ (GDK_LOCK_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD5_MASK)) & 0xfff;
- if (event->keyval == GDK_n && !event->state) {
+ if ((maskedstate == GDK_MOD1_MASK || maskedstate == 0) && event->keyval == GDK_n) {
// button for that one is not in toolbar anymore, so handle it manually
deadbeef->sendmessage (M_PLAYRANDOM, 0, 0, 0);
}
@@ -560,7 +560,6 @@ on_seekbar_button_release_event (GtkWidget *widget,
GdkEventButton *event)
{
seekbar_moving = 0;
- gtk_widget_queue_draw (widget);
DB_playItem_t *trk = deadbeef->streamer_get_playing_track ();
if (trk) {
float time = (event->x - widget->allocation.x) * deadbeef->pl_get_item_duration (trk) / (widget->allocation.width);
@@ -570,6 +569,7 @@ on_seekbar_button_release_event (GtkWidget *widget,
deadbeef->streamer_seek (time);
deadbeef->pl_item_unref (trk);
}
+ gtk_widget_queue_draw (widget);
return FALSE;
}
diff --git a/plugins/gtkui/ddbtabstrip.c b/plugins/gtkui/ddbtabstrip.c
index 36b80443..ea7905c7 100644
--- a/plugins/gtkui/ddbtabstrip.c
+++ b/plugins/gtkui/ddbtabstrip.c
@@ -632,7 +632,6 @@ on_rename_playlist1_activate (GtkMenuItem *menuitem,
if (res == GTK_RESPONSE_OK) {
const char *text = gtk_entry_get_text (GTK_ENTRY (e));
deadbeef->plt_set_title (tab_clicked, text);
- extern GtkWidget *mainwin;
}
gtk_widget_destroy (dlg);
}
@@ -659,31 +658,6 @@ on_add_new_playlist1_activate (GtkMenuItem *menuitem,
}
}
-#if 0
-void
-on_load_playlist1_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
-
-}
-
-
-void
-on_save_playlist1_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
-
-}
-
-
-void
-on_save_all_playlists1_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
-
-}
-#endif
-
GtkWidget*
create_plmenu (void)
{
@@ -720,73 +694,17 @@ create_plmenu (void)
G_CALLBACK (on_add_new_playlist1_activate),
NULL);
-
-#if 0
- separator11 = gtk_separator_menu_item_new ();
- gtk_widget_show (separator11);
- gtk_container_add (GTK_CONTAINER (plmenu), separator11);
- gtk_widget_set_sensitive (separator11, FALSE);
-
- load_playlist1 = gtk_menu_item_new_with_mnemonic ("Load Playlist");
- gtk_widget_show (load_playlist1);
- gtk_container_add (GTK_CONTAINER (plmenu), load_playlist1);
-
- save_playlist1 = gtk_menu_item_new_with_mnemonic ("Save Playlist");
- gtk_widget_show (save_playlist1);
- gtk_container_add (GTK_CONTAINER (plmenu), save_playlist1);
-
- save_all_playlists1 = gtk_menu_item_new_with_mnemonic ("Save All Playlists");
- gtk_widget_show (save_all_playlists1);
- gtk_container_add (GTK_CONTAINER (plmenu), save_all_playlists1);
-
- g_signal_connect ((gpointer) load_playlist1, "activate",
- G_CALLBACK (on_load_playlist1_activate),
- NULL);
- g_signal_connect ((gpointer) save_playlist1, "activate",
- G_CALLBACK (on_save_playlist1_activate),
- NULL);
- g_signal_connect ((gpointer) save_all_playlists1, "activate",
- G_CALLBACK (on_save_all_playlists1_activate),
- NULL);
-#endif
-
/* Store pointers to all widgets, for use by lookup_widget(). */
GLADE_HOOKUP_OBJECT_NO_REF (plmenu, plmenu, "plmenu");
GLADE_HOOKUP_OBJECT (plmenu, rename_playlist1, "rename_playlist1");
GLADE_HOOKUP_OBJECT (plmenu, remove_playlist1, "remove_playlist1");
GLADE_HOOKUP_OBJECT (plmenu, add_new_playlist1, "add_new_playlist1");
-// GLADE_HOOKUP_OBJECT (plmenu, separator11, "separator11");
-// GLADE_HOOKUP_OBJECT (plmenu, load_playlist1, "load_playlist1");
-// GLADE_HOOKUP_OBJECT (plmenu, save_playlist1, "save_playlist1");
-// GLADE_HOOKUP_OBJECT (plmenu, save_all_playlists1, "save_all_playlists1");
return plmenu;
}
static void
tabstrip_scroll_left (DdbTabStrip *ts) {
-#if 0
- // scroll to leftmost border-spanning tab
- int scrollsize = 0;
- int w = 0;
- int cnt = deadbeef->plt_get_count ();
- for (int idx = 0; idx < cnt; idx++) {
- int tab_w = ddb_tabstrip_get_tab_width (ts, idx);
- if (w < ts->hscrollpos && w + tab_w >= ts->hscrollpos) {
- scrollsize = ts->hscrollpos - w;
- break;
- }
- w += tab_w - tab_overlap_size;
- }
- w += tab_overlap_size + 3;
-
- ts->hscrollpos -= scrollsize;
- if (ts->hscrollpos < 0) {
- ts->hscrollpos = 0;
- }
- deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos);
- gtk_widget_queue_draw (GTK_WIDGET (ts));
-#endif
int tab = deadbeef->plt_get_curr ();
if (tab > 0) {
tab--;
@@ -797,29 +715,6 @@ tabstrip_scroll_left (DdbTabStrip *ts) {
static void
tabstrip_scroll_right (DdbTabStrip *ts) {
-#if 0
- // scroll to rightmost border-spanning tab
- GtkWidget *widget = GTK_WIDGET (ts);
- int scrollsize = 0;
- int w = 0;
- int cnt = deadbeef->plt_get_count ();
- int boundary = widget->allocation.width - arrow_widget_width*2 + ts->hscrollpos;
- for (int idx = 0; idx < cnt; idx++) {
- int tab_w = ddb_tabstrip_get_tab_width (ts, idx);
-
- if (scrollsize == 0 && w < boundary && w + tab_w >= boundary) {
- scrollsize = (w + tab_w) - boundary;
- }
- w += tab_w - tab_overlap_size;
- }
- w += tab_overlap_size + 3;
- ts->hscrollpos += scrollsize;
- if (ts->hscrollpos > w - (widget->allocation.width - arrow_widget_width*2)) {
- ts->hscrollpos = w - (widget->allocation.width - arrow_widget_width*2);
- }
- deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos);
- gtk_widget_queue_draw (widget);
-#endif
int tab = deadbeef->plt_get_curr ();
if (tab < deadbeef->plt_get_count ()-1) {
tab++;
@@ -920,7 +815,7 @@ on_tabstrip_button_press_event (GtkWidget *widget,
}
return FALSE;
}
- else if (deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 0)) {
+ else if (deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 1)) {
if (tab_clicked != -1) {
deadbeef->plt_remove (tab_clicked);
int playlist = deadbeef->plt_get_curr ();
diff --git a/plugins/gtkui/deadbeef.glade b/plugins/gtkui/deadbeef.glade
index 08f73098..4f38af30 100644
--- a/plugins/gtkui/deadbeef.glade
+++ b/plugins/gtkui/deadbeef.glade
@@ -1252,76 +1252,14 @@
</child>
<child>
- <widget class="GtkButton" id="button1">
- <property name="width_request">83</property>
+ <widget class="GtkButton" id="button3">
<property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
- <signal name="clicked" handler="on_progress_abort" last_modification_time="Sun, 16 Aug 2009 17:17:12 GMT"/>
-
- <child>
- <widget class="GtkAlignment" id="alignment10">
- <property name="visible">True</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xscale">0</property>
- <property name="yscale">0</property>
- <property name="top_padding">0</property>
- <property name="bottom_padding">0</property>
- <property name="left_padding">0</property>
- <property name="right_padding">0</property>
-
- <child>
- <widget class="GtkHBox" id="hbox51">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">2</property>
-
- <child>
- <widget class="GtkImage" id="image389">
- <property name="visible">True</property>
- <property name="stock">gtk-stop</property>
- <property name="icon_size">4</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xpad">0</property>
- <property name="ypad">0</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkLabel" id="label87">
- <property name="visible">True</property>
- <property name="label" translatable="yes">_Abort</property>
- <property name="use_underline">True</property>
- <property name="use_markup">False</property>
- <property name="justify">GTK_JUSTIFY_LEFT</property>
- <property name="wrap">False</property>
- <property name="selectable">False</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xpad">0</property>
- <property name="ypad">0</property>
- <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
- <property name="width_chars">-1</property>
- <property name="single_line_mode">False</property>
- <property name="angle">0</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
- </widget>
- </child>
- </widget>
- </child>
+ <signal name="clicked" handler="on_progress_abort" last_modification_time="Mon, 25 Oct 2010 20:04:28 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
@@ -3063,7 +3001,7 @@ Album</property>
<property name="show_tabs">True</property>
<property name="show_border">True</property>
<property name="tab_pos">GTK_POS_TOP</property>
- <property name="scrollable">False</property>
+ <property name="scrollable">True</property>
<property name="enable_popup">False</property>
<child>
diff --git a/plugins/gtkui/gtkui.c b/plugins/gtkui/gtkui.c
index 2dd40a73..1c2b9b9f 100644
--- a/plugins/gtkui/gtkui.c
+++ b/plugins/gtkui/gtkui.c
@@ -778,22 +778,23 @@ on_add_location_activate (GtkMenuItem *menuitem,
static void
songchanged (DdbListview *ps, DB_playItem_t *from, DB_playItem_t *to) {
int plt = deadbeef->plt_get_curr ();
-#if 0 // this breaks redraw when playqueue switches to another playlist
- int str_plt = deadbeef->streamer_get_current_playlist ();
- if (plt != str_plt) {
- // have nothing to do here -- active playlist is not the one with playing song
- return;
- }
-#endif
int to_idx = -1;
if (!ddb_listview_is_scrolling (ps) && to) {
- to_idx = deadbeef->pl_get_idx_of (to);
- if (to_idx != -1) {
- if (deadbeef->conf_get_int ("playlist.scroll.followplayback", 0)) {
- ddb_listview_scroll_to (ps, to_idx);
+ int cursor_follows_playback = deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 0);
+ int scroll_follows_playback = deadbeef->conf_get_int ("playlist.scroll.followplayback", 0);
+ int plt = deadbeef->streamer_get_current_playlist ();
+ if (plt != -1) {
+ if (cursor_follows_playback && plt != deadbeef->plt_get_curr ()) {
+ deadbeef->plt_set_curr (plt);
}
- if (deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 0)) {
- ddb_listview_set_cursor_noscroll (ps, to_idx);
+ to_idx = deadbeef->pl_get_idx_of (to);
+ if (to_idx != -1) {
+ if (cursor_follows_playback) {
+ ddb_listview_set_cursor_noscroll (ps, to_idx);
+ }
+ if (scroll_follows_playback && plt == deadbeef->plt_get_curr ()) {
+ ddb_listview_scroll_to (ps, to_idx);
+ }
}
}
}
@@ -1071,9 +1072,16 @@ void
gtkui_focus_on_playing_track (void) {
DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
if (it) {
+ int plt = deadbeef->streamer_get_current_playlist ();
+ if (plt != deadbeef->plt_get_curr ()) {
+ deadbeef->plt_set_curr (plt);
+ }
int idx = deadbeef->pl_get_idx_of (it);
- ddb_listview_scroll_to (DDB_LISTVIEW (lookup_widget (mainwin, "playlist")), idx);
- ddb_listview_set_cursor (DDB_LISTVIEW (lookup_widget (mainwin, "playlist")), idx);
+ if (idx != -1) {
+ DdbListview *pl = DDB_LISTVIEW (lookup_widget (mainwin, "playlist"));
+ ddb_listview_scroll_to (pl, idx);
+ ddb_listview_set_cursor (pl, idx);
+ }
deadbeef->pl_item_unref (it);
}
}
diff --git a/plugins/gtkui/interface.c b/plugins/gtkui/interface.c
index 6ee99477..1b255c52 100644
--- a/plugins/gtkui/interface.c
+++ b/plugins/gtkui/interface.c
@@ -1034,11 +1034,7 @@ create_addprogress (void)
GtkWidget *progresstitle;
GtkWidget *hbox7;
GtkWidget *label22;
- GtkWidget *button1;
- GtkWidget *alignment10;
- GtkWidget *hbox51;
- GtkWidget *image389;
- GtkWidget *label87;
+ GtkWidget *button3;
addprogress = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (addprogress), 12);
@@ -1068,32 +1064,14 @@ create_addprogress (void)
gtk_widget_show (label22);
gtk_box_pack_start (GTK_BOX (hbox7), label22, TRUE, FALSE, 0);
- button1 = gtk_button_new ();
- gtk_widget_show (button1);
- gtk_box_pack_start (GTK_BOX (hbox7), button1, FALSE, FALSE, 0);
- gtk_widget_set_size_request (button1, 83, -1);
- GTK_WIDGET_UNSET_FLAGS (button1, GTK_CAN_FOCUS);
-
- alignment10 = gtk_alignment_new (0.5, 0.5, 0, 0);
- gtk_widget_show (alignment10);
- gtk_container_add (GTK_CONTAINER (button1), alignment10);
-
- hbox51 = gtk_hbox_new (FALSE, 2);
- gtk_widget_show (hbox51);
- gtk_container_add (GTK_CONTAINER (alignment10), hbox51);
-
- image389 = gtk_image_new_from_stock ("gtk-stop", GTK_ICON_SIZE_BUTTON);
- gtk_widget_show (image389);
- gtk_box_pack_start (GTK_BOX (hbox51), image389, FALSE, FALSE, 0);
-
- label87 = gtk_label_new_with_mnemonic (_("_Abort"));
- gtk_widget_show (label87);
- gtk_box_pack_start (GTK_BOX (hbox51), label87, FALSE, FALSE, 0);
+ button3 = gtk_button_new_from_stock ("gtk-cancel");
+ gtk_widget_show (button3);
+ gtk_box_pack_start (GTK_BOX (hbox7), button3, FALSE, FALSE, 0);
g_signal_connect ((gpointer) addprogress, "delete_event",
G_CALLBACK (on_addprogress_delete_event),
NULL);
- g_signal_connect ((gpointer) button1, "clicked",
+ g_signal_connect ((gpointer) button3, "clicked",
G_CALLBACK (on_progress_abort),
NULL);
@@ -1103,11 +1081,7 @@ create_addprogress (void)
GLADE_HOOKUP_OBJECT (addprogress, progresstitle, "progresstitle");
GLADE_HOOKUP_OBJECT (addprogress, hbox7, "hbox7");
GLADE_HOOKUP_OBJECT (addprogress, label22, "label22");
- GLADE_HOOKUP_OBJECT (addprogress, button1, "button1");
- GLADE_HOOKUP_OBJECT (addprogress, alignment10, "alignment10");
- GLADE_HOOKUP_OBJECT (addprogress, hbox51, "hbox51");
- GLADE_HOOKUP_OBJECT (addprogress, image389, "image389");
- GLADE_HOOKUP_OBJECT (addprogress, label87, "label87");
+ GLADE_HOOKUP_OBJECT (addprogress, button3, "button3");
return addprogress;
}
@@ -1908,6 +1882,7 @@ create_prefwin (void)
gtk_widget_show (notebook4);
gtk_container_add (GTK_CONTAINER (notebook), notebook4);
gtk_container_set_border_width (GTK_CONTAINER (notebook4), 12);
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook4), TRUE);
vbox21 = gtk_vbox_new (FALSE, 8);
gtk_widget_show (vbox21);
diff --git a/plugins/gtkui/prefwin.c b/plugins/gtkui/prefwin.c
index c2b8a63a..4e1c42b0 100644
--- a/plugins/gtkui/prefwin.c
+++ b/plugins/gtkui/prefwin.c
@@ -521,7 +521,7 @@ on_preferences_activate (GtkMenuItem *menuitem,
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "hide_tray_icon")), deadbeef->conf_get_int ("gtkui.hide_tray_icon", 0));
// mmb_delete_playlist
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "mmb_delete_playlist")), deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 0));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "mmb_delete_playlist")), deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 1));
// embolden current track
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "embolden_current")), deadbeef->conf_get_int ("gtkui.embolden_current_track", 0));
diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c
index 27c6f6f0..4a234e8f 100644
--- a/plugins/lastfm/lastfm.c
+++ b/plugins/lastfm/lastfm.c
@@ -119,6 +119,7 @@ curl_req_send (const char *req, const char *post) {
memset(lfm_err, 0, sizeof(lfm_err));
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, lfm_err);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, lfm_curl_control);
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0);
if (post) {
diff --git a/plugins/mms/Makefile.am b/plugins/mms/Makefile.am
index c6a06fdb..f16f7412 100644
--- a/plugins/mms/Makefile.am
+++ b/plugins/mms/Makefile.am
@@ -1,7 +1,20 @@
if HAVE_MMS
mmsdir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = mms.la
-mms_la_SOURCES = mms.c
+mms_la_SOURCES = mmsplug.c\
+ libmms/mms.c\
+ libmms/mmsh.c\
+ libmms/mmsx.c\
+ libmms/uri.c\
+ libmms/asfheader.h\
+ libmms/bswap.h\
+ libmms/mms-common.h\
+ libmms/mms.h\
+ libmms/mmsh.h\
+ libmms/mmsio.h\
+ libmms/mmsx.h\
+ libmms/uri.h
+
mms_la_LDFLAGS = -module
mms_la_LIBADD = $(LDADD) $(LIBMMS_LIBS)
diff --git a/plugins/mms/libmms/AUTHORS b/plugins/mms/libmms/AUTHORS
new file mode 100644
index 00000000..04a68431
--- /dev/null
+++ b/plugins/mms/libmms/AUTHORS
@@ -0,0 +1,6 @@
+Original author of the MMS interface code was Major MMS of http://www.geocities.com/majormms/
+Enhanced and maintained by Xine project at http://xine.sf.net
+
+Current developers of libmms are:
+Maciej Katafiasz (Mathrick) <mathrick@users.sourceforge.net>
+Søren Hansen (shawarma) <sh@warma.dk>
diff --git a/plugins/mms/libmms/COPYING.LIB b/plugins/mms/libmms/COPYING.LIB
new file mode 100644
index 00000000..b124cf58
--- /dev/null
+++ b/plugins/mms/libmms/COPYING.LIB
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/plugins/mms/libmms/README b/plugins/mms/libmms/README
new file mode 100644
index 00000000..8426d2cf
--- /dev/null
+++ b/plugins/mms/libmms/README
@@ -0,0 +1,57 @@
+ LibMMS
+
+ library for parsing
+ Microsoft Media Streaming
+ protocol
+
+
+What is LibMMS?
+===============
+
+LibMMS is common library for parsing mms:// and mmsh:// type network
+streams. These are commonly used to stream Windows Media Video content
+over the web. LibMMS itself is only for receiving MMS stream, it
+doesn't handle sending at all. If you need streaming functionality,
+look for other protocol, such as RT(S)P. MMS is legacy thing, being
+closed with no specs and abandoned even by its creator, the Microsoft Corp.
+
+Why LibMMS?
+===========
+
+LibMMS was created out of need for common library that would remedy
+current situation where each Free Software project maintains its own
+implementation of MMS protocol handling, creating unnecessary code and
+effort duplication and introducing feature disparity as not every
+bugfix gets into each of them. It also addresses need for LGPL
+licensed code for that task, as every current implementation is
+licensed as GPL, thus knocking out many projects that do not use GPL
+themselves.
+
+Howto LibMMS?
+=============
+
+LibMMS is intended to be small and simple, being useful for any
+project that needs MMS handling functionality. It is constructed in
+such a way that allows plugging custom I/O implementation, thus being
+easy to integrate with custom framework, and providing a way to add
+timeouted or cancelable I/O etc.
+
+Hmm, you said no specs? How so?
+===============================
+
+LibMMS code is based on amazing work done by SDP guys (http://get.to/sdp)
+Without the specs they've reverse-engineered, there won't be any free
+implementation of MMS handling today.
+
+How is LibMMS licensed?
+=======================
+
+LibMMS is Free Software, licensed under GNU Library General Public
+License. Original code comes from Xine project (http://xine.sf.net),
+and it got separated for number of reasons, one of them being desire
+to provide non GPL projects with library they could use. There's a
+number of valuable LGPL projects locked out by GPL libraries, besides
+we strongly feel that GPL is bad license for a library, as it's not
+library thing to mess with its users licensing by vague interpretations
+of wether linking makes binary derived work. We want libraries to be
+used, and for that, we need to stay clean. \ No newline at end of file
diff --git a/plugins/mms/libmms/README.LICENSE b/plugins/mms/libmms/README.LICENSE
new file mode 100644
index 00000000..15dfbd25
--- /dev/null
+++ b/plugins/mms/libmms/README.LICENSE
@@ -0,0 +1,6 @@
+Original GPL code was taken from Xine project
+(http://xine.sf.net). Relicensed to LGPL with explicit approval from
+all copyright holders, if you're interested, you can see thread at:
+
+<FIXME: add link to xine's mailing archives, import mails from my
+local mailbox> \ No newline at end of file
diff --git a/plugins/mms/libmms/asfheader.h b/plugins/mms/libmms/asfheader.h
new file mode 100644
index 00000000..2aaffb39
--- /dev/null
+++ b/plugins/mms/libmms/asfheader.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2000-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: asfheader.h,v 1.2 2005/12/12 09:24:19 theuraeus Exp $
+ *
+ * demultiplexer for asf streams
+ *
+ * based on ffmpeg's
+ * ASF compatible encoder and decoder.
+ * Copyright (c) 2000, 2001 Gerard Lantau.
+ *
+ * GUID list from avifile
+ * some other ideas from MPlayer
+ */
+
+#ifndef ASFHEADER_H
+#define ASFHEADER_H
+
+/*
+ * define asf GUIDs (list from avifile)
+ */
+#define GUID_ERROR 0
+
+ /* base ASF objects */
+#define GUID_ASF_HEADER 1
+#define GUID_ASF_DATA 2
+#define GUID_ASF_SIMPLE_INDEX 3
+#define GUID_INDEX 4
+#define GUID_MEDIA_OBJECT_INDEX 5
+#define GUID_TIMECODE_INDEX 6
+
+ /* header ASF objects */
+#define GUID_ASF_FILE_PROPERTIES 7
+#define GUID_ASF_STREAM_PROPERTIES 8
+#define GUID_ASF_HEADER_EXTENSION 9
+#define GUID_ASF_CODEC_LIST 10
+#define GUID_ASF_SCRIPT_COMMAND 11
+#define GUID_ASF_MARKER 12
+#define GUID_ASF_BITRATE_MUTUAL_EXCLUSION 13
+#define GUID_ASF_ERROR_CORRECTION 14
+#define GUID_ASF_CONTENT_DESCRIPTION 15
+#define GUID_ASF_EXTENDED_CONTENT_DESCRIPTION 16
+#define GUID_ASF_STREAM_BITRATE_PROPERTIES 17
+#define GUID_ASF_EXTENDED_CONTENT_ENCRYPTION 18
+#define GUID_ASF_PADDING 19
+
+ /* stream properties object stream type */
+#define GUID_ASF_AUDIO_MEDIA 20
+#define GUID_ASF_VIDEO_MEDIA 21
+#define GUID_ASF_COMMAND_MEDIA 22
+#define GUID_ASF_JFIF_MEDIA 23
+#define GUID_ASF_DEGRADABLE_JPEG_MEDIA 24
+#define GUID_ASF_FILE_TRANSFER_MEDIA 25
+#define GUID_ASF_BINARY_MEDIA 26
+
+ /* stream properties object error correction type */
+#define GUID_ASF_NO_ERROR_CORRECTION 27
+#define GUID_ASF_AUDIO_SPREAD 28
+
+ /* mutual exclusion object exlusion type */
+#define GUID_ASF_MUTEX_BITRATE 29
+#define GUID_ASF_MUTEX_UKNOWN 30
+
+ /* header extension */
+#define GUID_ASF_RESERVED_1 31
+
+ /* script command */
+#define GUID_ASF_RESERVED_SCRIPT_COMMNAND 32
+
+ /* marker object */
+#define GUID_ASF_RESERVED_MARKER 33
+
+ /* various */
+/*
+#define GUID_ASF_HEAD2 27
+*/
+#define GUID_ASF_AUDIO_CONCEAL_NONE 34
+#define GUID_ASF_CODEC_COMMENT1_HEADER 35
+#define GUID_ASF_2_0_HEADER 36
+#define GUID_ASF_EXTENDED_STREAM_PROPERTIES 37
+
+#define GUID_END 38
+
+
+/* asf stream types */
+#define ASF_STREAM_TYPE_UNKNOWN 0
+#define ASF_STREAM_TYPE_AUDIO 1
+#define ASF_STREAM_TYPE_VIDEO 2
+#define ASF_STREAM_TYPE_CONTROL 3
+#define ASF_STREAM_TYPE_JFIF 4
+#define ASF_STREAM_TYPE_DEGRADABLE_JPEG 5
+#define ASF_STREAM_TYPE_FILE_TRANSFER 6
+#define ASF_STREAM_TYPE_BINARY 7
+
+#define ASF_MAX_NUM_STREAMS 23
+
+#ifndef GUID_DEFINED
+#define GUID_DEFINED
+
+typedef struct _GUID { /* size is 16 */
+ uint32_t Data1;
+ uint16_t Data2;
+ uint16_t Data3;
+ uint8_t Data4[8];
+} GUID;
+
+#endif /* !GUID_DEFINED */
+
+static const struct
+{
+ const char* name;
+ const GUID guid;
+} guids[] =
+{
+ { "error",
+ { 0x0,} },
+
+
+ /* base ASF objects */
+ { "header",
+ { 0x75b22630, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} },
+
+ { "data",
+ { 0x75b22636, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} },
+
+ { "simple index",
+ { 0x33000890, 0xe5b1, 0x11cf, { 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb }} },
+
+ { "index",
+ { 0xd6e229d3, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} },
+
+ { "media object index",
+ { 0xfeb103f8, 0x12ad, 0x4c64, { 0x84, 0x0f, 0x2a, 0x1d, 0x2f, 0x7a, 0xd4, 0x8c }} },
+
+ { "timecode index",
+ { 0x3cb73fd0, 0x0c4a, 0x4803, { 0x95, 0x3d, 0xed, 0xf7, 0xb6, 0x22, 0x8f, 0x0c }} },
+
+ /* header ASF objects */
+ { "file properties",
+ { 0x8cabdca1, 0xa947, 0x11cf, { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+
+ { "stream header",
+ { 0xb7dc0791, 0xa9b7, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+
+ { "header extension",
+ { 0x5fbf03b5, 0xa92e, 0x11cf, { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+
+ { "codec list",
+ { 0x86d15240, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} },
+
+ { "script command",
+ { 0x1efb1a30, 0x0b62, 0x11d0, { 0xa3, 0x9b, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} },
+
+ { "marker",
+ { 0xf487cd01, 0xa951, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+
+ { "bitrate mutual exclusion",
+ { 0xd6e229dc, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} },
+
+ { "error correction",
+ { 0x75b22635, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} },
+
+ { "content description",
+ { 0x75b22633, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} },
+
+ { "extended content description",
+ { 0xd2d0a440, 0xe307, 0x11d2, { 0x97, 0xf0, 0x00, 0xa0, 0xc9, 0x5e, 0xa8, 0x50 }} },
+
+ { "stream bitrate properties", /* (http://get.to/sdp) */
+ { 0x7bf875ce, 0x468d, 0x11d1, { 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 }} },
+
+ { "extended content encryption",
+ { 0x298ae614, 0x2622, 0x4c17, { 0xb9, 0x35, 0xda, 0xe0, 0x7e, 0xe9, 0x28, 0x9c }} },
+
+ { "padding",
+ { 0x1806d474, 0xcadf, 0x4509, { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 }} },
+
+
+ /* stream properties object stream type */
+ { "audio media",
+ { 0xf8699e40, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} },
+
+ { "video media",
+ { 0xbc19efc0, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} },
+
+ { "command media",
+ { 0x59dacfc0, 0x59e6, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} },
+
+ { "JFIF media (JPEG)",
+ { 0xb61be100, 0x5b4e, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} },
+
+ { "Degradable JPEG media",
+ { 0x35907de0, 0xe415, 0x11cf, { 0xa9, 0x17, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} },
+
+ { "File Transfer media",
+ { 0x91bd222c, 0xf21c, 0x497a, { 0x8b, 0x6d, 0x5a, 0xa8, 0x6b, 0xfc, 0x01, 0x85 }} },
+
+ { "Binary media",
+ { 0x3afb65e2, 0x47ef, 0x40f2, { 0xac, 0x2c, 0x70, 0xa9, 0x0d, 0x71, 0xd3, 0x43 }} },
+
+ /* stream properties object error correction */
+ { "no error correction",
+ { 0x20fb5700, 0x5b55, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} },
+
+ { "audio spread",
+ { 0xbfc3cd50, 0x618f, 0x11cf, { 0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20 }} },
+
+
+ /* mutual exclusion object exlusion type */
+ { "mutex bitrate",
+ { 0xd6e22a01, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} },
+
+ { "mutex unknown",
+ { 0xd6e22a02, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} },
+
+
+ /* header extension */
+ { "reserved_1",
+ { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+
+
+ /* script command */
+ { "reserved script command",
+ { 0x4B1ACBE3, 0x100B, 0x11D0, { 0xA3, 0x9B, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }} },
+
+ /* marker object */
+ { "reserved marker",
+ { 0x4CFEDB20, 0x75F6, 0x11CF, { 0x9C, 0x0F, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB }} },
+
+ /* various */
+ /* Already defined (reserved_1)
+ { "head2",
+ { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} },
+ */
+ { "audio conceal none",
+ { 0x49f1a440, 0x4ece, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} },
+
+ { "codec comment1 header",
+ { 0x86d15241, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} },
+
+ { "asf 2.0 header",
+ { 0xd6e229d1, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} },
+
+ { "extended stream properties",
+ { 0x14e6a5cb, 0xc672, 0x4332, { 0x83, 0x99, 0xa9, 0x69, 0x52, 0x06, 0x5b, 0x5a }} },
+
+};
+
+#endif
diff --git a/plugins/mms/libmms/bswap.h b/plugins/mms/libmms/bswap.h
new file mode 100644
index 00000000..6a6e1d10
--- /dev/null
+++ b/plugins/mms/libmms/bswap.h
@@ -0,0 +1,282 @@
+#ifndef BSWAP_H_INCLUDED
+#define BSWAP_H_INCLUDED
+
+/*
+ * Copyright (C) 2004 Maciej Katafiasz <mathrick@users.sourceforge.net>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* NOTE:
+ * Now, to clear up confusion: LE_XX means "from LE to native, XX bits wide"
+ * I know it's not very clear naming (tell me about it, I
+ * misinterpreted in first version and caused bad nasty bug, *sigh*),
+ * but that's inherited code, will clean up as things go
+ * Oh, and one more thing -- they take *pointers*, not actual ints
+ */
+
+/* Basic bit swapping functions
+ */
+#define GUINT16_SWAP_LE_BE_CONSTANT(val) ((guint16) ( \
+ (guint16) ((guint16) (val) >> 8) | \
+ (guint16) ((guint16) (val) << 8)))
+
+#define GUINT32_SWAP_LE_BE_CONSTANT(val) ((guint32) ( \
+ (((guint32) (val) & (guint32) 0x000000ffU) << 24) | \
+ (((guint32) (val) & (guint32) 0x0000ff00U) << 8) | \
+ (((guint32) (val) & (guint32) 0x00ff0000U) >> 8) | \
+ (((guint32) (val) & (guint32) 0xff000000U) >> 24)))
+
+#define GUINT64_SWAP_LE_BE_CONSTANT(val) ((guint64) ( \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x00000000000000ffU)) << 56) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x000000000000ff00U)) << 40) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x0000000000ff0000U)) << 24) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x00000000ff000000U)) << 8) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x000000ff00000000U)) >> 8) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x0000ff0000000000U)) >> 24) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0x00ff000000000000U)) >> 40) | \
+ (((guint64) (val) & \
+ (guint64) G_GINT64_CONSTANT (0xff00000000000000U)) >> 56)))
+
+/* Arch specific stuff for speed
+ */
+#if defined (__GNUC__) && (__GNUC__ >= 2) && defined (__OPTIMIZE__)
+# if defined (__i386__)
+# define GUINT16_SWAP_LE_BE_IA32(val) \
+ (__extension__ \
+ ({ register guint16 __v, __x = ((guint16) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ ("rorw $8, %w0" \
+ : "=r" (__v) \
+ : "0" (__x) \
+ : "cc"); \
+ __v; }))
+# if !defined (__i486__) && !defined (__i586__) \
+ && !defined (__pentium__) && !defined (__i686__) \
+ && !defined (__pentiumpro__) && !defined (__pentium4__)
+# define GUINT32_SWAP_LE_BE_IA32(val) \
+ (__extension__ \
+ ({ register guint32 __v, __x = ((guint32) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ ("rorw $8, %w0\n\t" \
+ "rorl $16, %0\n\t" \
+ "rorw $8, %w0" \
+ : "=r" (__v) \
+ : "0" (__x) \
+ : "cc"); \
+ __v; }))
+# else /* 486 and higher has bswap */
+# define GUINT32_SWAP_LE_BE_IA32(val) \
+ (__extension__ \
+ ({ register guint32 __v, __x = ((guint32) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ ("bswap %0" \
+ : "=r" (__v) \
+ : "0" (__x)); \
+ __v; }))
+# endif /* processor specific 32-bit stuff */
+# define GUINT64_SWAP_LE_BE_IA32(val) \
+ (__extension__ \
+ ({ union { guint64 __ll; \
+ guint32 __l[2]; } __w, __r; \
+ __w.__ll = ((guint64) (val)); \
+ if (__builtin_constant_p (__w.__ll)) \
+ __r.__ll = GUINT64_SWAP_LE_BE_CONSTANT (__w.__ll); \
+ else \
+ { \
+ __r.__l[0] = GUINT32_SWAP_LE_BE (__w.__l[1]); \
+ __r.__l[1] = GUINT32_SWAP_LE_BE (__w.__l[0]); \
+ } \
+ __r.__ll; }))
+ /* Possibly just use the constant version and let gcc figure it out? */
+# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA32 (val))
+# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA32 (val))
+# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA32 (val))
+# elif defined (__ia64__)
+# define GUINT16_SWAP_LE_BE_IA64(val) \
+ (__extension__ \
+ ({ register guint16 __v, __x = ((guint16) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ __volatile__ ("shl %0 = %1, 48 ;;" \
+ "mux1 %0 = %0, @rev ;;" \
+ : "=r" (__v) \
+ : "r" (__x)); \
+ __v; }))
+# define GUINT32_SWAP_LE_BE_IA64(val) \
+ (__extension__ \
+ ({ register guint32 __v, __x = ((guint32) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ __volatile__ ("shl %0 = %1, 32 ;;" \
+ "mux1 %0 = %0, @rev ;;" \
+ : "=r" (__v) \
+ : "r" (__x)); \
+ __v; }))
+# define GUINT64_SWAP_LE_BE_IA64(val) \
+ (__extension__ \
+ ({ register guint64 __v, __x = ((guint64) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ __volatile__ ("mux1 %0 = %1, @rev ;;" \
+ : "=r" (__v) \
+ : "r" (__x)); \
+ __v; }))
+# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA64 (val))
+# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA64 (val))
+# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA64 (val))
+# elif defined (__x86_64__)
+# define GUINT32_SWAP_LE_BE_X86_64(val) \
+ (__extension__ \
+ ({ register guint32 __v, __x = ((guint32) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ ("bswapl %0" \
+ : "=r" (__v) \
+ : "0" (__x)); \
+ __v; }))
+# define GUINT64_SWAP_LE_BE_X86_64(val) \
+ (__extension__ \
+ ({ register guint64 __v, __x = ((guint64) (val)); \
+ if (__builtin_constant_p (__x)) \
+ __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \
+ else \
+ __asm__ ("bswapq %0" \
+ : "=r" (__v) \
+ : "0" (__x)); \
+ __v; }))
+ /* gcc seems to figure out optimal code for this on its own */
+# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val))
+# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_X86_64 (val))
+# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_X86_64 (val))
+# else /* generic gcc */
+# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val))
+# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val))
+# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val))
+# endif
+#else /* generic */
+# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val))
+# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val))
+# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val))
+#endif /* generic */
+
+#define GUINT16_SWAP_LE_PDP(val) ((guint16) (val))
+#define GUINT16_SWAP_BE_PDP(val) (GUINT16_SWAP_LE_BE (val))
+#define GUINT32_SWAP_LE_PDP(val) ((guint32) ( \
+ (((guint32) (val) & (guint32) 0x0000ffffU) << 16) | \
+ (((guint32) (val) & (guint32) 0xffff0000U) >> 16)))
+#define GUINT32_SWAP_BE_PDP(val) ((guint32) ( \
+ (((guint32) (val) & (guint32) 0x00ff00ffU) << 8) | \
+ (((guint32) (val) & (guint32) 0xff00ff00U) >> 8)))
+
+
+/* The G*_TO_?E() macros are defined in glibconfig.h.
+ * The transformation is symmetric, so the FROM just maps to the TO.
+ */
+#define GINT16_FROM_LE(val) (GINT16_TO_LE (val))
+#define GUINT16_FROM_LE(val) (GUINT16_TO_LE (val))
+#define GINT16_FROM_BE(val) (GINT16_TO_BE (val))
+#define GUINT16_FROM_BE(val) (GUINT16_TO_BE (val))
+#define GINT32_FROM_LE(val) (GINT32_TO_LE (val))
+#define GUINT32_FROM_LE(val) (GUINT32_TO_LE (val))
+#define GINT32_FROM_BE(val) (GINT32_TO_BE (val))
+#define GUINT32_FROM_BE(val) (GUINT32_TO_BE (val))
+
+#define GINT64_FROM_LE(val) (GINT64_TO_LE (val))
+#define GUINT64_FROM_LE(val) (GUINT64_TO_LE (val))
+#define GINT64_FROM_BE(val) (GINT64_TO_BE (val))
+#define GUINT64_FROM_BE(val) (GUINT64_TO_BE (val))
+
+#define GLONG_FROM_LE(val) (GLONG_TO_LE (val))
+#define GULONG_FROM_LE(val) (GULONG_TO_LE (val))
+#define GLONG_FROM_BE(val) (GLONG_TO_BE (val))
+#define GULONG_FROM_BE(val) (GULONG_TO_BE (val))
+
+#define GINT_FROM_LE(val) (GINT_TO_LE (val))
+#define GUINT_FROM_LE(val) (GUINT_TO_LE (val))
+#define GINT_FROM_BE(val) (GINT_TO_BE (val))
+#define GUINT_FROM_BE(val) (GUINT_TO_BE (val))
+
+#define GSIZE_FROM_LE(val) (GSIZE_TO_LE (val))
+#define GSSIZE_FROM_LE(val) (GSSIZE_TO_LE (val))
+#define GSIZE_FROM_BE(val) (GSIZE_TO_BE (val))
+#define GSSIZE_FROM_BE(val) (GSSIZE_TO_BE (val))
+
+
+/* Portable versions of host-network order stuff
+ */
+#define g_ntohl(val) (GUINT32_FROM_BE (val))
+#define g_ntohs(val) (GUINT16_FROM_BE (val))
+#define g_htonl(val) (GUINT32_TO_BE (val))
+#define g_htons(val) (GUINT16_TO_BE (val))
+
+#include <../../config.h>
+
+#if WORDS_BIGENDIAN
+#define GUINT64_TO_BE(val) (val)
+#define GUINT32_TO_BE(val) (val)
+#define GUINT16_TO_BE(val) (val)
+#define GUINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val)
+#define GUINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val)
+#define GUINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val)
+#define GINT64_TO_BE(val) (val)
+#define GINT32_TO_BE(val) (val)
+#define GINT16_TO_BE(val) (val)
+#define GINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val)
+#define GINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val)
+#define GINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val)
+#else
+#define GUINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val)
+#define GUINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val)
+#define GUINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val)
+#define GUINT64_TO_LE(val) (val)
+#define GUINT32_TO_LE(val) (val)
+#define GUINT16_TO_LE(val) (val)
+#define GINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val)
+#define GINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val)
+#define GINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val)
+#define GINT64_TO_LE(val) (val)
+#define GINT32_TO_LE(val) (val)
+#define GINT16_TO_LE(val) (val)
+#endif
+
+#define LE_16(val) (GINT16_FROM_LE (*((u_int16_t*)(val))))
+#define BE_16(val) (GINT16_FROM_BE (*((u_int16_t*)(val))))
+#define LE_32(val) (GINT32_FROM_LE (*((u_int32_t*)(val))))
+#define BE_32(val) (GINT32_FROM_BE (*((u_int32_t*)(val))))
+
+#define LE_64(val) (GINT64_FROM_LE (*((u_int64_t*)(val))))
+#define BE_64(val) (GINT64_FROM_BE (*((u_int64_t*)(val))))
+
+#endif /* BSWAP_H_INCLUDED */
diff --git a/plugins/mms/libmms/mms-common.h b/plugins/mms/libmms/mms-common.h
new file mode 100644
index 00000000..444d35a0
--- /dev/null
+++ b/plugins/mms/libmms/mms-common.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Hans de Goede <j.w.r.degoede@hhs.nl>
+ *
+ * This file is part of libmms a free mms protocol library
+ *
+ * libmms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libmss 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 Library 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
+ */
+
+/* This file contains code which is shared between the mms and mmsh protocol
+ handling code. */
+
+#ifndef __MMS_COMMON_H
+#define __MMS_COMMON_H
+
+typedef struct mms_stream_s mms_stream_t;
+struct mms_stream_s {
+ int stream_id;
+ int stream_type;
+ uint32_t bitrate;
+ uint32_t bitrate_pos;
+};
+
+#endif
diff --git a/plugins/mms/libmms/mms.c b/plugins/mms/libmms/mms.c
new file mode 100644
index 00000000..7e0d4a07
--- /dev/null
+++ b/plugins/mms/libmms/mms.c
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2002-2004 the xine project
+ *
+ * This file is part of LibMMS, an MMS protocol handling library.
+ *
+ * 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 ree 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: mms.c,v 1.31 2007/12/11 20:35:01 jwrdegoede Exp $
+ *
+ * MMS over TCP protocol
+ * based on work from major mms
+ * utility functions to handle communication with an mms server
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+#include <stdio.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 <iconv.h>
+
+/********** logging **********/
+#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__)
+
+#define __MMS_C__
+
+#include "bswap.h"
+#include "mms.h"
+#include "asfheader.h"
+#include "uri.h"
+#include "mms-common.h"
+
+/*
+ * mms specific types
+ */
+
+#define MMST_PORT 1755
+
+#define BUF_SIZE 102400
+
+#define CMD_HEADER_LEN 40
+#define CMD_PREFIX_LEN 8
+#define CMD_BODY_LEN 1024 * 16 /* FIXME: make this dynamic */
+
+#define ASF_HEADER_LEN (8192 * 2)
+
+
+#define MMS_PACKET_ERR 0
+#define MMS_PACKET_COMMAND 1
+#define MMS_PACKET_ASF_HEADER 2
+#define MMS_PACKET_ASF_PACKET 3
+
+#define ASF_HEADER_PACKET_ID_TYPE 2
+#define ASF_MEDIA_PACKET_ID_TYPE 4
+
+
+typedef struct mms_buffer_s mms_buffer_t;
+struct mms_buffer_s {
+ uint8_t *buffer;
+ int pos;
+};
+
+typedef struct mms_packet_header_s mms_packet_header_t;
+struct mms_packet_header_s {
+ uint32_t packet_len;
+ uint8_t flags;
+ uint8_t packet_id_type;
+ uint32_t packet_seq;
+};
+
+struct mms_s {
+
+ int s;
+
+ /* url parsing */
+ GURI *guri;
+ char *url;
+ char *proto;
+ char *host;
+ int port;
+ char *user;
+ char *password;
+ char *uri;
+
+ /* command to send */
+ char scmd[CMD_HEADER_LEN + CMD_BODY_LEN];
+ char *scmd_body; /* pointer to &scmd[CMD_HEADER_LEN] */
+ int scmd_len; /* num bytes written in header */
+
+ char str[1024]; /* scratch buffer to built strings */
+
+ /* receive buffer */
+ uint8_t buf[BUF_SIZE];
+ int buf_size;
+ int buf_read;
+ off_t buf_packet_seq_offset; /* packet sequence offset residing in
+ buf */
+
+ uint8_t asf_header[ASF_HEADER_LEN];
+ uint32_t asf_header_len;
+ uint32_t asf_header_read;
+ int seq_num;
+ int num_stream_ids;
+ mms_stream_t streams[ASF_MAX_NUM_STREAMS];
+ uint8_t packet_id_type;
+ off_t start_packet_seq; /* for live streams != 0, need to keep it around */
+ int need_discont; /* whether we need to set start_packet_seq */
+ uint32_t asf_packet_len;
+ uint64_t file_len;
+ uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */
+ uint64_t preroll;
+ uint64_t asf_num_packets;
+ char guid[37];
+ int bandwidth;
+
+ int has_audio;
+ int has_video;
+ int live_flag;
+ int seekable;
+ off_t current_pos;
+ int eos;
+};
+
+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; /* EOF */
+ if(ret < 0) {
+ lprintf("mms: 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("mms: unable to resolve host: %s\n", host);
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s == -1) {
+ lprintf("mms: failed to create socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) {
+ lprintf("mms: 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))
+
+const mms_io_t* mms_get_default_io_impl()
+{
+ return &default_io;
+}
+
+void mms_set_default_io_impl(const mms_io_t *io)
+{
+ if(io->select)
+ {
+ default_io.select = io->select;
+ default_io.select_data = io->select_data;
+ } else
+ {
+ default_io.select = fallback_io.select;
+ default_io.select_data = fallback_io.select_data;
+ }
+ if(io->read)
+ {
+ default_io.read = io->read;
+ default_io.read_data = io->read_data;
+ } else
+ {
+ default_io.read = fallback_io.read;
+ default_io.read_data = fallback_io.read_data;
+ }
+ if(io->write)
+ {
+ default_io.write = io->write;
+ default_io.write_data = io->write_data;
+ } else
+ {
+ default_io.write = fallback_io.write;
+ default_io.write_data = fallback_io.write_data;
+ }
+ if(io->connect)
+ {
+ default_io.connect = io->connect;
+ default_io.connect_data = io->connect_data;
+ } else
+ {
+ default_io.connect = fallback_io.connect;
+ default_io.connect_data = fallback_io.connect_data;
+ }
+}
+
+static void mms_buffer_init (mms_buffer_t *mms_buffer, uint8_t *buffer) {
+ mms_buffer->buffer = buffer;
+ mms_buffer->pos = 0;
+}
+
+static void mms_buffer_put_8 (mms_buffer_t *mms_buffer, uint8_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+
+ mms_buffer->pos += 1;
+}
+
+#if 0
+static void mms_buffer_put_16 (mms_buffer_t *mms_buffer, uint16_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff;
+
+ mms_buffer->pos += 2;
+}
+#endif
+
+static void mms_buffer_put_32 (mms_buffer_t *mms_buffer, uint32_t value) {
+
+ mms_buffer->buffer[mms_buffer->pos] = value & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 2] = (value >> 16) & 0xff;
+ mms_buffer->buffer[mms_buffer->pos + 3] = (value >> 24) & 0xff;
+
+ mms_buffer->pos += 4;
+}
+
+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("mms: GUID: %s\n", guids[i].name);
+ return i;
+ }
+ }
+
+ lprintf("mms: 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 void print_command (char *data, int len) {
+
+#ifdef DEBUG
+ int i;
+ int dir = LE_32 (data + 36) >> 16;
+ int comm = LE_32 (data + 36) & 0xFFFF;
+
+ lprintf ("----------------------------------------------\n");
+ if (dir == 3) {
+ lprintf ("send command 0x%02x, %d bytes\n", comm, len);
+ } else {
+ lprintf ("receive command 0x%02x, %d bytes\n", comm, len);
+ }
+ lprintf (" start sequence %08x\n", LE_32 (data + 0));
+ lprintf (" command id %08x\n", LE_32 (data + 4));
+ lprintf (" length %8x \n", LE_32 (data + 8));
+ lprintf (" protocol %08x\n", LE_32 (data + 12));
+ lprintf (" len8 %8x \n", LE_32 (data + 16));
+ lprintf (" sequence # %08x\n", LE_32 (data + 20));
+ lprintf (" len8 (II) %8x \n", LE_32 (data + 32));
+ lprintf (" dir | comm %08x\n", LE_32 (data + 36));
+ if (len >= 4)
+ lprintf (" prefix1 %08x\n", LE_32 (data + 40));
+ if (len >= 8)
+ lprintf (" prefix2 %08x\n", LE_32 (data + 44));
+
+ for (i = (CMD_HEADER_LEN + CMD_PREFIX_LEN); i < (CMD_HEADER_LEN + CMD_PREFIX_LEN + len); i += 1) {
+ unsigned char c = data[i];
+
+ if ((c >= 32) && (c < 128))
+ lprintf ("%c", c);
+ else
+ lprintf (" %02x ", c);
+
+ }
+ if (len > CMD_HEADER_LEN)
+ lprintf ("\n");
+ lprintf ("----------------------------------------------\n");
+#endif
+}
+
+
+
+static int send_command (mms_io_t *io, mms_t *this, int command,
+ uint32_t prefix1, uint32_t prefix2,
+ int length) {
+ int len8;
+ off_t n;
+ mms_buffer_t command_buffer;
+
+ len8 = (length + 7) / 8;
+
+ this->scmd_len = 0;
+
+ mms_buffer_init(&command_buffer, this->scmd);
+ mms_buffer_put_32 (&command_buffer, 0x00000001); /* start sequence */
+ mms_buffer_put_32 (&command_buffer, 0xB00BFACE); /* #-)) */
+ mms_buffer_put_32 (&command_buffer, len8 * 8 + 32);
+ mms_buffer_put_32 (&command_buffer, 0x20534d4d); /* protocol type "MMS " */
+ mms_buffer_put_32 (&command_buffer, len8 + 4);
+ mms_buffer_put_32 (&command_buffer, this->seq_num);
+ this->seq_num++;
+ mms_buffer_put_32 (&command_buffer, 0x0); /* timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x0);
+ mms_buffer_put_32 (&command_buffer, len8 + 2);
+ mms_buffer_put_32 (&command_buffer, 0x00030000 | command); /* dir | command */
+ /* end of the 40 byte command header */
+
+ mms_buffer_put_32 (&command_buffer, prefix1);
+ mms_buffer_put_32 (&command_buffer, prefix2);
+
+ if (length & 7)
+ memset(this->scmd + length + CMD_HEADER_LEN + CMD_PREFIX_LEN, 0, 8 - (length & 7));
+
+ n = io_write(io, this->s, this->scmd, len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN);
+ if (n != (len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN)) {
+ return 0;
+ }
+
+ print_command (this->scmd, length);
+
+ return 1;
+}
+
+static int string_utf16(iconv_t url_conv, char *dest, char *src, int dest_len)
+{
+ char *ip = src, *op = dest;
+ size_t ip_len = strlen(src);
+ size_t op_len = dest_len - 2; /* reserve 2 bytes for 0 termination */
+
+ if (iconv(url_conv, &ip, &ip_len, &op, &op_len) == (size_t)-1) {
+ lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno));
+ return 0;
+ }
+
+ /* 0 terminate the string */
+ *op++ = 0;
+ *op++ = 0;
+
+ return op - dest;
+}
+
+/*
+ * return packet type
+ */
+static int get_packet_header (mms_io_t *io, mms_t *this, mms_packet_header_t *header) {
+ size_t len;
+ int packet_type;
+
+ header->packet_len = 0;
+ header->packet_seq = 0;
+ header->flags = 0;
+ header->packet_id_type = 0;
+ len = io_read(io, this->s, this->buf, 8);
+ this->buf_packet_seq_offset = -1;
+ if (len != 8)
+ goto error;
+
+ if (LE_32(this->buf + 4) == 0xb00bface) {
+ /* command packet */
+ header->flags = this->buf[3];
+ len = io_read(io, this->s, this->buf + 8, 4);
+ if (len != 4)
+ goto error;
+
+ header->packet_len = LE_32(this->buf + 8) + 4;
+ if (header->packet_len > BUF_SIZE - 12) {
+ lprintf("mms: get_packet_header error cmd packet length > bufsize\n");
+ header->packet_len = 0;
+ return MMS_PACKET_ERR;
+ }
+ packet_type = MMS_PACKET_COMMAND;
+ } else {
+ header->packet_seq = LE_32(this->buf);
+ header->packet_id_type = this->buf[4];
+ header->flags = this->buf[5];
+ header->packet_len = (LE_16(this->buf + 6) - 8) & 0xffff;
+ if (header->packet_id_type == ASF_HEADER_PACKET_ID_TYPE) {
+ packet_type = MMS_PACKET_ASF_HEADER;
+ } else {
+ packet_type = MMS_PACKET_ASF_PACKET;
+ }
+ }
+
+ return packet_type;
+
+error:
+ lprintf("mms: error reading packet header\n");
+ return MMS_PACKET_ERR;
+}
+
+
+static int get_packet_command (mms_io_t *io, mms_t *this, uint32_t packet_len) {
+
+
+ int command = 0;
+ size_t len;
+
+ len = io_read(io, this->s, this->buf + 12, packet_len) ;
+ //this->buf_packet_seq_offset = -1; // already set in get_packet_header
+ if (len != packet_len) {
+ lprintf("mms: error reading command packet\n");
+ return 0;
+ }
+
+ print_command (this->buf, len);
+
+ /* check protocol type ("MMS ") */
+ if (LE_32(this->buf + 12) != 0x20534D4D) {
+ lprintf("mms: unknown protocol type: %c%c%c%c (0x%08X)\n",
+ this->buf[12], this->buf[13], this->buf[14], this->buf[15],
+ LE_32(this->buf + 12));
+ return 0;
+ }
+
+ command = LE_32 (this->buf + 36) & 0xFFFF;
+ lprintf("mms: received command = %02x, len: %d\n", command, packet_len);
+
+ return command;
+}
+
+static int get_answer (mms_io_t *io, mms_t *this) {
+ int command = 0;
+ mms_packet_header_t header;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ break;
+ case MMS_PACKET_COMMAND:
+ command = get_packet_command (io, this, header.packet_len);
+ if (command == 0)
+ return 0;
+
+ if (command == 0x1b) {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ /* FIXME: limit recursion */
+ command = get_answer (io, this);
+ }
+ break;
+ case MMS_PACKET_ASF_HEADER:
+ lprintf("mms: unexpected asf header packet\n");
+ break;
+ case MMS_PACKET_ASF_PACKET:
+ lprintf("mms: unexpected asf packet\n");
+ break;
+ }
+
+ return command;
+}
+
+
+static int get_asf_header (mms_io_t *io, mms_t *this) {
+
+ off_t len;
+ int stop = 0;
+
+ this->asf_header_read = 0;
+ this->asf_header_len = 0;
+
+ while (!stop) {
+ mms_packet_header_t header;
+ int command;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ return 0;
+ case MMS_PACKET_COMMAND:
+ command = get_packet_command (io, this, header.packet_len);
+ if (command == 0)
+ return 0;
+
+ if (command == 0x1b) {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ command = get_answer (io, this);
+ } else {
+ lprintf("mms: unexpected command packet\n");
+ }
+ break;
+ case MMS_PACKET_ASF_HEADER:
+ case MMS_PACKET_ASF_PACKET:
+ if (header.packet_len + this->asf_header_len > ASF_HEADER_LEN) {
+ lprintf("mms: asf packet too large: %d\n",
+ header.packet_len + this->asf_header_len);
+ return 0;
+ }
+ len = io_read(io, this->s,
+ this->asf_header + this->asf_header_len, header.packet_len);
+ if (len != header.packet_len) {
+ lprintf("mms: error reading asf header\n");
+ return 0;
+ }
+ this->asf_header_len += header.packet_len;
+ lprintf("mms: header flags: %d\n", header.flags);
+ if ((header.flags == 0X08) || (header.flags == 0X0C))
+ stop = 1;
+ break;
+ }
+ }
+ return 1;
+}
+
+static void interp_stream_properties(mms_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("mms: stream object, stream id: %d, type: %d, encrypted: %d\n",
+ stream_id, type, encrypted);
+
+ if (this->num_stream_ids < ASF_MAX_NUM_STREAMS) {
+ this->streams[this->num_stream_ids].stream_type = type;
+ this->streams[this->num_stream_ids].stream_id = stream_id;
+ this->num_stream_ids++;
+ } else {
+ lprintf("mms: too many streams, skipping\n");
+ }
+}
+
+static void interp_asf_header (mms_t *this) {
+
+ int i;
+
+ this->asf_packet_len = 0;
+ this->num_stream_ids = 0;
+ this->asf_num_packets = 0;
+ /*
+ * parse 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->asf_packet_len = LE_32(this->asf_header + i + 92);
+ if (this->asf_packet_len > BUF_SIZE) {
+ lprintf("mms: asf packet len too large: %d\n", this->asf_packet_len);
+ this->asf_packet_len = 0;
+ break;
+ }
+ this->file_len = 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("mms: file object, packet length = %d (%d)\n",
+ this->asf_packet_len, 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;
+
+ for(j = 0; j < streams; j++) {
+ int stream_index;
+ 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("mms: stream id %d, bitrate %d\n", stream_id,
+ this->streams[stream_index].bitrate);
+ } else
+ lprintf ("mms: 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("mms: 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("mms: l: %d\n", l);
+ lprintf("mms: Stream No: %d\n", stream_no);
+ lprintf("mms: 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("mms: Language id index: %d\n", lang_id_index);
+ lprintf("mms: 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("mms: 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("mms: 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);
+ break;
+ }
+
+ lprintf("mms: length: %llu\n", (unsigned long long)length);
+ i += length;
+ }
+}
+
+const static char *const mmst_proto_s[] = { "mms", "mmst", NULL };
+
+static int mmst_valid_proto (char *proto) {
+ int i = 0;
+
+ if (!proto)
+ return 0;
+
+ while(mmst_proto_s[i]) {
+ if (!strcasecmp(proto, mmst_proto_s[i])) {
+ return 1;
+ }
+ i++;
+ }
+ return 0;
+}
+
+/*
+ * returns 1 on error
+ */
+static int mms_tcp_connect(mms_io_t *io, mms_t *this) {
+ if (!this->port) this->port = MMST_PORT;
+
+ /*
+ * try to connect
+ */
+ lprintf("mms: trying to connect to %s on port %d\n", this->host, this->port);
+ this->s = io_connect(io, this->host, this->port);
+ if (this->s == -1) {
+ lprintf("mms: failed to connect to %s\n", this->host);
+ return 1;
+ }
+
+ lprintf("mms: connected\n");
+ return 0;
+}
+
+static void mms_gen_guid(char guid[]) {
+ static char digit[16] = "0123456789ABCDEF";
+ int i = 0;
+
+ srand(time(NULL));
+ for (i = 0; i < 36; i++) {
+ guid[i] = digit[(int) ((16.0*rand())/(RAND_MAX+1.0))];
+ }
+ guid[8] = '-'; guid[13] = '-'; guid[18] = '-'; guid[23] = '-';
+ guid[36] = '\0';
+}
+
+const char *status_to_string(int status)
+{
+ switch (status) {
+ case 0x80070003:
+ return "Path not found";
+ case 0x80070005:
+ return "Access Denied";
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * return 0 on error
+ */
+int static mms_choose_best_streams(mms_io_t *io, mms_t *this) {
+ int i;
+ int video_stream = -1;
+ int audio_stream = -1;
+ int max_arate = 0;
+ int min_vrate = 0;
+ int min_bw_left = 0;
+ int bandwitdh_left;
+ int res;
+
+ /* command 0x33 */
+ /* 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 (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->bandwidth - max_arate;
+ if (bandwitdh_left < 0) {
+ bandwitdh_left = 0;
+ }
+ lprintf("mms: bandwidth %d, left %d\n", this->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 lower bitrate of */
+ 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 ((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("mms: selected streams: audio %d, video %d\n", audio_stream, video_stream);
+ memset (this->scmd_body, 0, 40);
+ for (i = 1; i < this->num_stream_ids; i++) {
+ this->scmd_body [ (i - 1) * 6 + 2 ] = 0xFF;
+ this->scmd_body [ (i - 1) * 6 + 3 ] = 0xFF;
+ this->scmd_body [ (i - 1) * 6 + 4 ] = this->streams[i].stream_id ;
+ this->scmd_body [ (i - 1) * 6 + 5 ] = this->streams[i].stream_id >> 8;
+ if ((this->streams[i].stream_id == audio_stream) ||
+ (this->streams[i].stream_id == video_stream)) {
+ this->scmd_body [ (i - 1) * 6 + 6 ] = 0x00;
+ this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
+ } else {
+ lprintf("mms: disabling stream %d\n", this->streams[i].stream_id);
+ this->scmd_body [ (i - 1) * 6 + 6 ] = 0x02;
+ this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
+
+ /* forces the asf demuxer to not choose this stream */
+ if (this->streams[i].bitrate_pos) {
+ if (this->streams[i].bitrate_pos+3 < ASF_HEADER_LEN) {
+ 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("mms: attempt to write beyond asf header limit\n");
+ }
+ }
+ }
+ }
+
+ lprintf("mms: send command 0x33\n");
+ if (!send_command (io, this, 0x33, this->num_stream_ids,
+ 0xFFFF | this->streams[0].stream_id << 16,
+ this->num_stream_ids * 6 + 2)) {
+ lprintf("mms: mms_choose_best_streams failed\n");
+ return 0;
+ }
+
+ if ((res = get_answer (io, this)) != 0x21) {
+ lprintf("mms: unexpected response: %02x (0x21)\n", res);
+ return 0;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x21 status: %08x (%s)\n",
+ res, status_to_string(res));
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * TODO: error messages
+ * network timing request
+ */
+/* FIXME: got somewhat broken during xine_stream_t->(void*) conversion */
+mms_t *mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth) {
+ iconv_t url_conv = (iconv_t)-1;
+ mms_t *this;
+ int res;
+ uint32_t openid;
+ mms_buffer_t command_buffer;
+
+ if (!url)
+ return NULL;
+
+ /* FIXME: needs proper error-signalling work */
+ this = (mms_t*) malloc (sizeof (mms_t));
+
+ this->url = strdup (url);
+ this->s = -1;
+ this->seq_num = 0;
+ this->scmd_body = this->scmd + CMD_HEADER_LEN + CMD_PREFIX_LEN;
+ this->asf_header_len = 0;
+ this->asf_header_read = 0;
+ this->num_stream_ids = 0;
+ this->asf_packet_len = 0;
+ this->start_packet_seq= 0;
+ this->need_discont = 1;
+ this->buf_size = 0;
+ this->buf_read = 0;
+ this->buf_packet_seq_offset = -1;
+ this->has_audio = 0;
+ this->has_video = 0;
+ this->bandwidth = bandwidth;
+ this->current_pos = 0;
+ this->eos = 0;
+
+ this->guri = gnet_uri_new(this->url);
+ if(!this->guri) {
+ lprintf("mms: invalid url\n");
+ goto fail;
+ }
+
+ /* MMS wants unescaped (so not percent coded) strings */
+ gnet_uri_unescape(this->guri);
+
+ this->proto = this->guri->scheme;
+ this->user = this->guri->user;
+ this->host = this->guri->hostname;
+ this->port = this->guri->port;
+ this->password = this->guri->passwd;
+ this->uri = gnet_mms_helper(this->guri, 0);
+
+ if(!this->uri)
+ goto fail;
+
+ if (!mmst_valid_proto(this->proto)) {
+ lprintf("mms: unsupported protocol: %s\n", this->proto);
+ goto fail;
+ }
+
+ if (mms_tcp_connect(io, this)) {
+ goto fail;
+ }
+
+ url_conv = iconv_open("UTF-16LE", "UTF-8");
+ if (url_conv == (iconv_t)-1) {
+ lprintf("mms: could not get iconv handle to convert url to unicode\n");
+ goto fail;
+ }
+
+ /*
+ * let the negotiations begin...
+ */
+
+ /* command 0x1 */
+ lprintf("mms: send command 0x01\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x0003001C);
+ mms_gen_guid(this->guid);
+ sprintf(this->str, "NSPlayer/7.0.0.1956; {%s}; Host: %s", this->guid,
+ this->host);
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, this->str,
+ CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 1, 0, 0x0004000b, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x01\n");
+ goto fail;
+ }
+
+ if ((res = get_answer (io, this)) != 0x01) {
+ lprintf("mms: unexpected response: %02x (0x01)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x01 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* TODO: insert network timing request here */
+ /* command 0x2 */
+ lprintf("mms: send command 0x02\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0x00989680);
+ mms_buffer_put_32 (&command_buffer, 0x00000002);
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos,
+ "\\\\192.168.0.129\\TCP\\1037",
+ CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 2, 0, 0xffffffff, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x02\n");
+ goto fail;
+ }
+
+ switch (res = get_answer (io, this)) {
+ case 0x02:
+ /* protocol accepted */
+ break;
+ case 0x03:
+ lprintf("mms: protocol failed\n");
+ goto fail;
+ default:
+ lprintf("mms: unexpected response: %02x (0x02 or 0x03)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x02 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* command 0x5 */
+ {
+ mms_buffer_t command_buffer;
+
+ lprintf("mms: send command 0x05\n");
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+
+ res = string_utf16(url_conv, this->scmd_body + command_buffer.pos,
+ this->uri, CMD_BODY_LEN - command_buffer.pos);
+ if(!res)
+ goto fail;
+
+ if (!send_command(io, this, 5, 1, 0, command_buffer.pos + res)) {
+ lprintf("mms: failed to send command 0x05\n");
+ goto fail;
+ }
+ }
+
+ switch (res = get_answer (io, this)) {
+ case 0x06:
+ {
+ int xx, yy;
+ /* no authentication required */
+ openid = LE_32(this->buf + 48);
+
+ /* Warning: sdp is not right here */
+ xx = this->buf[62];
+ yy = this->buf[63];
+ this->live_flag = ((xx == 0) && ((yy & 0xf) == 2));
+ this->seekable = !this->live_flag;
+ lprintf("mms: openid=%d, live: live_flag=%d, xx=%d, yy=%d\n", openid, this->live_flag, xx, yy);
+ }
+ break;
+ case 0x1A:
+ /* authentication request, not yet supported */
+ lprintf("mms: authentication request, not yet supported\n");
+ goto fail;
+ break;
+ default:
+ lprintf("mms: unexpected response: %02x (0x06 or 0x1A)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x06 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ /* command 0x15 */
+ lprintf("mms: send command 0x15\n");
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00008000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0x40AC2000); /* ?? */
+ mms_buffer_put_32 (&command_buffer, ASF_HEADER_PACKET_ID_TYPE); /* Header Packet ID type */
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
+ if (!send_command (io, this, 0x15, openid, 0, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x15\n");
+ goto fail;
+ }
+ }
+
+ if ((res = get_answer (io, this)) != 0x11) {
+ lprintf("mms: unexpected response: %02x (0x11)\n", res);
+ goto fail;
+ }
+
+ res = LE_32(this->buf + 40);
+ if (res != 0) {
+ lprintf("mms: error answer 0x11 status: %08x (%s)\n",
+ res, status_to_string(res));
+ goto fail;
+ }
+
+ this->num_stream_ids = 0;
+
+ if (!get_asf_header (io, this))
+ goto fail;
+
+ interp_asf_header (this);
+ if (!this->asf_packet_len || !this->num_stream_ids)
+ goto fail;
+
+ if (!mms_choose_best_streams(io, this)) {
+ lprintf("mms: mms_choose_best_streams failed\n");
+ goto fail;
+ }
+
+ /* command 0x07 */
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE;
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ goto fail;
+ }
+ }
+
+ iconv_close(url_conv);
+ lprintf("mms: connect: passed\n");
+
+ return this;
+
+fail:
+ if (this->s != -1)
+ close (this->s);
+ if (this->url)
+ free(this->url);
+ if (this->guri)
+ gnet_uri_delete(this->guri);
+ if (this->uri)
+ free(this->uri);
+ if (url_conv != (iconv_t)-1)
+ iconv_close(url_conv);
+
+ free (this);
+ return NULL;
+}
+
+static int get_media_packet (mms_io_t *io, mms_t *this) {
+ mms_packet_header_t header;
+ off_t len;
+
+ switch (get_packet_header (io, this, &header)) {
+ case MMS_PACKET_ERR:
+ return 0;
+
+ case MMS_PACKET_COMMAND:
+ {
+ int command;
+ command = get_packet_command (io, this, header.packet_len);
+
+ switch (command) {
+ case 0:
+ return 0;
+
+ case 0x1e:
+ {
+ uint32_t error_code;
+
+ /* Warning: sdp is incomplete. Do not stop if error_code==1 */
+ error_code = LE_32(this->buf + CMD_HEADER_LEN);
+ lprintf("mms: End of the current stream. Continue=%d\n", error_code);
+
+ if (error_code == 0) {
+ this->eos = 1;
+ return 0;
+ }
+
+ }
+ break;
+
+ case 0x20:
+ {
+ lprintf("mms: new stream.\n");
+ /* asf header */
+ if (!get_asf_header (io, this)) {
+ lprintf("mms: failed to read new ASF header\n");
+ return 0;
+ }
+
+ interp_asf_header (this);
+ if (!this->asf_packet_len || !this->num_stream_ids)
+ return 0;
+
+ if (!mms_choose_best_streams(io, this))
+ return 0;
+
+ /* send command 0x07 */
+ /* TODO: ugly */
+ /* command 0x07 */
+ {
+ mms_buffer_t command_buffer;
+ mms_buffer_init(&command_buffer, this->scmd_body);
+ mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ mms_buffer_put_32 (&command_buffer, 0x00000000);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0xFF);
+ mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ return 0;
+ }
+ }
+ this->current_pos = 0;
+
+ /* I don't know if this ever happens with none live (and thus
+ seekable streams), but I do know that if it happens all bets
+ with regards to seeking are off */
+ this->seekable = 0;
+ }
+ break;
+
+ case 0x1b:
+ {
+ if (!send_command (io, this, 0x1b, 0, 0, 0)) {
+ lprintf("mms: error sending ping reply\n");
+ return 0;
+ }
+ }
+ break;
+
+ case 0x05:
+ break;
+
+ default:
+ lprintf("mms: unexpected mms command %02x\n", command);
+ }
+ this->buf_size = 0;
+ }
+ break;
+
+ case MMS_PACKET_ASF_HEADER:
+ lprintf("mms: unexpected asf header packet\n");
+ this->buf_size = 0;
+ break;
+
+ case MMS_PACKET_ASF_PACKET:
+ {
+ /* media packet */
+
+ /* FIXME: probably needs some more sophisticated logic, but
+ until we do seeking, this should work */
+ if(this->need_discont &&
+ header.packet_id_type == ASF_MEDIA_PACKET_ID_TYPE)
+ {
+ this->need_discont = 0;
+ this->start_packet_seq = header.packet_seq;
+ }
+
+ if (header.packet_len > this->asf_packet_len) {
+ lprintf("mms: invalid asf packet len: %d bytes\n", header.packet_len);
+ return 0;
+ }
+
+ /* simulate a seek */
+ this->current_pos = (off_t)this->asf_header_len +
+ ((off_t)header.packet_seq - this->start_packet_seq) * (off_t)this->asf_packet_len;
+
+ len = io_read(io, this->s, this->buf, header.packet_len);
+ if (len != header.packet_len) {
+ lprintf("mms: error reading asf packet\n");
+ return 0;
+ }
+
+ /* explicit padding with 0 */
+ memset(this->buf + header.packet_len, 0, this->asf_packet_len - header.packet_len);
+ if (header.packet_id_type == this->packet_id_type) {
+ this->buf_size = this->asf_packet_len;
+ this->buf_packet_seq_offset =
+ header.packet_seq - this->start_packet_seq;
+ } else {
+ this->buf_size = 0;
+ // Don't set this packet sequence for reuse in seek(), since the
+ // subsequence packet may be discontinued.
+ //this->buf_packet_seq_offset = header.packet_seq;
+ // already set to -1 in get_packet_header
+ //this->buf_packet_seq_offset = -1;
+ }
+ }
+ break;
+ }
+
+ return 1;
+}
+
+
+int mms_peek_header (mms_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 mms_read (mms_io_t *io, mms_t *this, char *data, int len) {
+ int total;
+
+ total = 0;
+ while (total < len && !this->eos) {
+
+ 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) {
+ this->buf_size = this->buf_read = 0;
+ if (!get_media_packet (io, this)) {
+ lprintf("mms: get_media_packet failed\n");
+ return total;
+ }
+ 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;
+}
+
+// To be inline function?
+static int mms_request_data_packet (mms_io_t *io, mms_t *this,
+ double time_sec, unsigned long first_packet, unsigned long time_msec_limit) {
+ /* command 0x07 */
+ {
+ mms_buffer_t command_buffer;
+ //mms_buffer_init(&command_buffer, this->scmd_body);
+ //mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */
+ //mms_buffer_put_32 (&command_buffer, 0x00000000);
+ memcpy(this->scmd_body, &time_sec, 8);
+ mms_buffer_init(&command_buffer, this->scmd_body+8);
+ mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */
+ //mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */
+ mms_buffer_put_32 (&command_buffer, first_packet); /* first packet sequence */
+ //mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */
+ //mms_buffer_put_8 (&command_buffer, 0xFF);
+ //mms_buffer_put_8 (&command_buffer, 0xFF);
+ //mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */
+ mms_buffer_put_32 (&command_buffer, time_msec_limit & 0x00FFFFFF);/* max stream time limit (3 bytes) */
+ mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */
+ if (!send_command (io, this, 0x07, 1, 0x0001FFFF, 8+command_buffer.pos)) {
+ lprintf("mms: failed to send command 0x07\n");
+ return 0;
+ }
+ }
+ /* TODO: adjust current_pos, considering asf_header_read */
+ return 1;
+}
+
+int mms_request_time_seek (mms_io_t *io, mms_t *this, double time_sec) {
+ if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE)
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1;
+ //return mms_request_data_packet (io, this, time_sec, 0xFFFFFFFF, 0x00FFFFFF);
+ // also adjust time by preroll
+ return mms_request_data_packet (io, this,
+ time_sec+(double)(this->preroll)/1000,
+ 0xFFFFFFFF, 0x00FFFFFF);
+}
+
+// set current_pos to the first byte of the requested packet by peeking at
+// the first packet.
+// To be inline function?
+static int peek_and_set_pos (mms_io_t *io, mms_t *this) {
+ uint8_t saved_buf[BUF_SIZE];
+ int saved_buf_size;
+ off_t saved_buf_packet_seq_offset;
+ // save buf and buf_size that may be changed in get_media_packet()
+ memcpy(saved_buf, this->buf, this->buf_size);
+ saved_buf_size = this->buf_size;
+ saved_buf_packet_seq_offset = this->buf_packet_seq_offset;
+ //this->buf_size = this->buf_read = 0; // reset buf, only if success peeking
+ this->buf_size = 0;
+ while (!this->eos) {
+ // get_media_packet() will set current_pos if data packet is read.
+ if (!get_media_packet (io, this)) {
+ lprintf("mms: get_media_packet failed\n");
+ // restore buf and buf_size that may be changed in get_media_packet()
+ memcpy(this->buf, saved_buf, saved_buf_size);
+ this->buf_size = saved_buf_size;
+ this->buf_packet_seq_offset = saved_buf_packet_seq_offset;
+ return 0;
+ }
+ if (this->buf_size > 0) break;
+ }
+ // flush header and reset buf_read, only if success peeking
+ this->asf_header_read = this->asf_header_len;
+ this->buf_read = 0;
+ return 1;
+ //return this->current_pos;
+}
+
+// send time seek request, and update current_pos corresponding to the next
+// requested packet
+// Note that, the current_pos will always does not less than asf_header_len
+int mms_time_seek (mms_io_t *io, mms_t *this, double time_sec) {
+ if (!this->seekable)
+ return 0;
+
+ if (!mms_request_time_seek (io, this, time_sec)) return 0;
+ return peek_and_set_pos (io, this);
+}
+
+// http://sdp.ppona.com/zipfiles/MMSprotocol_pdf.zip said that, this
+// packet_seq value make no difference in version 9 servers.
+// But from my experiment with
+// mms://202.142.200.130/tltk/56k/tltkD2006-08-08ID-7209.wmv and
+// mms://202.142.200.130/tltk/56k/tltkD2006-09-01ID-7467.wmv (the url may valid
+// in only 2-3 months) whose server is version 9, it does response and return
+// the requested packet.
+int mms_request_packet_seek (mms_io_t *io, mms_t *this,
+ unsigned long packet_seq) {
+ if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE)
+ this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1;
+ return mms_request_data_packet (io, this, 0, packet_seq, 0x00FFFFFF);
+}
+
+// send packet seek request, and update current_pos corresponding to the next
+// requested packet
+// Note that, the current_pos will always does not less than asf_header_len
+// Not export this function. Let user use mms_seek() instead?
+static int mms_packet_seek (mms_io_t *io, mms_t *this,
+ unsigned long packet_seq) {
+ if (!mms_request_packet_seek (io, this, packet_seq)) return 0;
+ return peek_and_set_pos (io, this);
+}
+
+/*
+TODO: To use this table to calculate buf_packet_seq_offset rather than store
+and retrieve it from this->buf_packet_seq_offset?
+current_packet_seq == (current_pos - asf_header_len) / asf_packet_len
+current_packet_seq == -1 if current_pos < asf_header_len
+buf_packet_seq_offset indicating which packet sequence are residing in the buf.
+Possible status after read(), "last" means last value or unchange.
+current_packet_seq | buf_read | buf_size | buf_packet_seq_offset
+-------------------+----------------+-----------+---------------
+<= 0 | 0 (last) | 0 (last) | none
+<= 0 | 0 (last) | 0 (last) | eos at #0
+<= 0 | 0 (last) | 0 (last) | eos at > #0
+<= 0 | 0 (last) | > 0 (last)| #0
+<= 0 | buf_size (last)| > 0 (last)| > #0
+> 0 | 0 | 0 | eos at current_packet_seq
+> 0 | 0(never happen)| > 0 | (never happen)
+> 0 | buf_size | > 0 | current_packet_seq-1
+*/
+// TODO: How to handle seek() in multi stream source?
+// The stream that follows 0x20 ("new stream") command.
+off_t mms_seek (mms_io_t *io, mms_t *this, off_t offset, int origin) {
+ off_t dest;
+ off_t dest_packet_seq;
+ //off_t buf_packet_seq_offset;
+
+ 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:
+ //if (this->asf_num_packets == 0) {
+ // //printf ("input_mms: unknown end position in seek!\n");
+ // return this->current_pos;
+ //}
+ dest = mms_get_length (this) + offset;
+ default:
+ printf ("input_mms: unknown origin in seek!\n");
+ return this->current_pos;
+ }
+
+ dest_packet_seq = dest - this->asf_header_len;
+ //if (dest_packet_seq > 0) dest_packet_seq /= this->asf_packet_len;
+ dest_packet_seq = dest_packet_seq >= 0 ?
+ dest_packet_seq / this->asf_packet_len : -1;
+#if 0
+ // buf_packet_seq_offset will identify which packet sequence are residing in
+ // the buf.
+#if 1 /* To show both of the alternate styles :D */
+ buf_packet_seq_offset = this->current_pos - this->asf_header_len;
+ //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len;
+ buf_packet_seq_offset = buf_packet_seq_offset >= 0 ?
+ buf_packet_seq_offset / this->asf_packet_len : -1;
+ // Note: buf_read == buf_size == 0 may means that it is eos,
+ // eos means that the packet has been peek'ed.
+ if (this->buf_read >= this->buf_size && this->buf_size > 0 &&
+ buf_packet_seq_offset >= 0 ||
+ // assuming packet not peek'ed in the following condition
+ /*this->buf_read >= this->buf_size && */this->buf_size == 0 &&
+ buf_packet_seq_offset == 0)
+ // The buf is all read but the packet has not been peek'ed.
+ --buf_packet_seq_offset;
+#else
+ buf_packet_seq_offset = this->current_pos - this->asf_header_len - 1;
+ //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len;
+ buf_packet_seq_offset = buf_packet_seq_offset >= 0 ?
+ buf_packet_seq_offset / this->asf_packet_len : -1;
+ // Note: buf_read == buf_size == 0 may means that it is eos,
+ // eos means that the packet has been peek'ed.
+ if (this->buf_read == 0/* && buf_packet_seq_offset >= 0*/)
+ // Since the packet has just been peek'ed.
+ ++buf_packet_seq_offset;
+#endif
+#endif
+
+ if (dest_packet_seq < 0) {
+ if (this->buf_packet_seq_offset > 0) {
+ if (!mms_request_packet_seek (io, this, 0xFFFFFFFF))
+ return this->current_pos;
+#if 1
+ // clear buf
+ this->buf_read = this->buf_size = 0;
+ this->buf_packet_seq_offset = -1;
+ } else {
+#else
+ // clear buf
+ this->buf_read = this->buf_size;
+ // Set this packet sequence not to be reused, since the subsequence
+ // packet may be discontinued.
+ this->buf_packet_seq_offset = -1;
+ // don't reset buf_read if buf_packet_seq_offset < 0, since the previous
+ // buf may not be cleared.
+ } else if (this->buf_packet_seq_offset == 0) {
+#endif
+ // 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->asf_packet_len) {
+ // 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;
+ }
+ if (dest_packet_seq != this->buf_packet_seq_offset) {
+ if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) {
+ // Do not seek beyond the last packet.
+ return this->current_pos;
+ }
+ if (!mms_packet_seek (io, this, this->start_packet_seq + dest_packet_seq))
+ return this->current_pos;
+ // Check if current_pos is correct.
+ // This can happen if the server ignore packet seek command.
+ // If so, just return unchanged current_pos, rather than trying to
+ // mms_read() to reach the destination pos.
+ // It should let the caller to decide to choose the alternate method, such
+ // as, mms_time_seek() and/or mms_read() until the destination pos is
+ // reached.
+ if (dest_packet_seq != this->buf_packet_seq_offset)
+ return this->current_pos;
+ // This has already been set in mms_packet_seek().
+ //if (current_packet_seq < 0)
+ // this->asf_header_read = this->asf_header_len;
+ //this->asf_header_read = this->asf_header_len;
+ }
+ // eos is reached ?
+ //if (this->buf_size <= 0) return this->current_pos;
+ //this->buf_read = (dest - this->asf_header_len) % this->asf_packet_len;
+ this->buf_read = dest -
+ (this->asf_header_len + dest_packet_seq*this->asf_packet_len);
+ // will never happen ?
+ //if (this->buf_size <= this->buf_read) return this->current_pos;
+ return this->current_pos = dest;
+}
+
+
+void mms_close (mms_t *this) {
+
+ if (this->s != -1)
+ close (this->s);
+ if (this->url)
+ free(this->url);
+ if (this->guri)
+ gnet_uri_delete(this->guri);
+ if (this->uri)
+ free(this->uri);
+
+ free (this);
+}
+
+double mms_get_time_length (mms_t *this) {
+ return (double)(this->time_len) / 1e7;
+}
+
+uint64_t mms_get_raw_time_length (mms_t *this) {
+ return this->time_len;
+}
+
+uint32_t mms_get_length (mms_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->asf_packet_len;
+ else
+ return this->file_len;
+}
+
+off_t mms_get_current_pos (mms_t *this) {
+ return this->current_pos;
+}
+
+uint32_t mms_get_asf_header_len (mms_t *this) {
+ return this->asf_header_len;
+}
+
+uint64_t mms_get_asf_packet_len (mms_t *this) {
+ return this->asf_packet_len;
+}
+
+int mms_get_seekable (mms_t *this) {
+ return this->seekable;
+}
diff --git a/plugins/mms/libmms/mms.h b/plugins/mms/libmms/mms.h
new file mode 100644
index 00000000..bce71d54
--- /dev/null
+++ b/plugins/mms/libmms/mms.h
@@ -0,0 +1,80 @@
+/*
+ * 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: mms.h,v 1.15 2007/12/11 20:24:48 jwrdegoede Exp $
+ *
+ * libmms public header
+ */
+
+#ifndef HAVE_MMS_H
+#define HAVE_MMS_H
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "mmsio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct mms_s mms_t;
+
+mms_t* mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth);
+
+int mms_read (mms_io_t *io, mms_t *instance, char *data, int len);
+int mms_request_time_seek (mms_io_t *io, mms_t *instance, double time_sec);
+int mms_time_seek (mms_io_t *io, mms_t *instance, double time_sec);
+int mms_request_packet_seek (mms_io_t *io, mms_t *instance,
+ unsigned long packet_seq);
+/*
+ * mms_seek() will try to seek using mms_request_packet_seek(), if the server
+ * ignore the packet seek command, it will return unchanged current_pos, rather
+ * than trying to mms_read() until the destination pos is reached. This is to
+ * let the caller, by itself, to decide to choose the alternate method, such
+ * as, mms_time_seek() and/or mms_read() until the destination pos is reached.
+ * One can do binary search using time offset (mms_time_seek()) as a search
+ * index, to approach the desired byte offset. It is to systematically guess
+ * the time offset to reach for the byte offset.
+ */
+mms_off_t mms_seek (mms_io_t *io, mms_t *instance, mms_off_t offset, int origin);
+/* return total playback time in seconds */
+double mms_get_time_length (mms_t *instance);
+/* return raw total playback time in 100 nanosecs (10^-7) */
+uint64_t mms_get_raw_time_length (mms_t *instance);
+uint32_t mms_get_length (mms_t *instance);
+void mms_close (mms_t *instance);
+
+int mms_peek_header (mms_t *instance, char *data, int maxsize);
+
+mms_off_t mms_get_current_pos (mms_t *instance);
+
+uint32_t mms_get_asf_header_len (mms_t *instance);
+
+uint64_t mms_get_asf_packet_len (mms_t *instance);
+
+int mms_get_seekable (mms_t *instance);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
+
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;
+}
diff --git a/plugins/mms/libmms/mmsh.h b/plugins/mms/libmms/mmsh.h
new file mode 100644
index 00000000..b222eeaa
--- /dev/null
+++ b/plugins/mms/libmms/mmsh.h
@@ -0,0 +1,63 @@
+/*
+ * 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.h,v 1.8 2007/12/11 20:24:48 jwrdegoede Exp $
+ *
+ * libmmsh public header
+ */
+
+#ifndef HAVE_MMSH_H
+#define HAVE_MMSH_H
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "mmsio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct mmsh_s mmsh_t;
+
+char* mmsh_connect_common(int *s ,int *port, char *url, char **host, char **path, char **file);
+mmsh_t* mmsh_connect (mms_io_t *io, void *data, const char *url_, int bandwidth);
+
+int mmsh_read (mms_io_t *io, mmsh_t *instance, char *data, int len);
+int mmsh_time_seek (mms_io_t *io, mmsh_t *instance, double time_sec);
+mms_off_t mmsh_seek (mms_io_t *io, mmsh_t *instance, mms_off_t offset, int origin);
+uint32_t mmsh_get_length (mmsh_t *instance);
+double mmsh_get_time_length (mmsh_t *instance);
+uint64_t mmsh_get_raw_time_length (mmsh_t *instance);
+mms_off_t mmsh_get_current_pos (mmsh_t *instance);
+void mmsh_close (mmsh_t *instance);
+
+int mmsh_peek_header (mmsh_t *instance, char *data, int maxsize);
+
+uint32_t mmsh_get_asf_header_len (mmsh_t *instance);
+
+uint32_t mmsh_get_asf_packet_len (mmsh_t *instance);
+
+int mmsh_get_seekable (mmsh_t *instance);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/plugins/mms/libmms/mmsio.h b/plugins/mms/libmms/mmsio.h
new file mode 100644
index 00000000..8e4304d9
--- /dev/null
+++ b/plugins/mms/libmms/mmsio.h
@@ -0,0 +1,93 @@
+#ifndef __MMS_IO_H__
+#define __MMS_IO_H__
+
+#define LIBMMS_HAVE_64BIT_OFF_T 1
+
+/* On 64 bit file offset capable systems, libmms' configure script adds
+ -D_FILE_OFFSET_BITS=64 to the CFLAGS. This causes off_t to be 64 bit,
+ When an app which includes this header file gets compiled without
+ -D_FILE_OFFSET_BITS=64, it should still expect / pass 64 bit ints for
+ off_t, this acomplishes this: */
+#if defined LIBMMS_HAVE_64BIT_OFF_T && !defined __MMS_C__
+#define mms_off_t int64_t
+#else
+#define mms_off_t off_t
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef mms_off_t (*mms_io_write_func)(void *data, int socket, char *buf, mms_off_t num);
+typedef mms_off_t (*mms_io_read_func)(void *data, int socket, char *buf, mms_off_t num);
+
+/* select states */
+#define MMS_IO_READ_READY 1
+#define MMS_IO_WRITE_READY 2
+
+enum
+ {
+ MMS_IO_STATUS_READY, /* IO can be safely performed */
+ MMS_IO_STATUS_ERROR, /* There was IO error */
+ MMS_IO_STATUS_ABORTED, /* IO command was (somehow)
+ aborted. This is not error, but invalidates IO for further operations*/
+ MMS_IO_STATUS_TIMEOUT /* Timeout was exceeded */
+ };
+
+/*
+ * Waits for a file descriptor/socket to change status.
+ *
+ * users can use this handler to provide their own implementations,
+ * for example abortable ones
+ *
+ * params :
+ * data whatever parameter may be needed by implementation
+ * fd file/socket descriptor
+ * state MMS_IO_READ_READY, MMS_IO_WRITE_READY
+ * timeout_sec timeout in seconds
+ *
+ *
+ * return value :
+ * MMS_IO_READY the file descriptor is ready for cmd
+ * MMS_IO_ERROR an i/o error occured
+ * MMS_IO_ABORTED command aborted
+ * MMS_IO_TIMEOUT the file descriptor is not ready after timeout_msec milliseconds
+ * every other return value is interpreted same as MMS_IO_ABORTED
+ */
+typedef int (*mms_io_select_func)(void *data, int fd, int state, int timeout_msec);
+
+/*
+ * open a tcp connection
+ *
+ * params :
+ * stream needed for reporting errors but may be NULL
+ * host address of target
+ * port port on target
+ *
+ * returns a socket descriptor or -1 if an error occured
+ */
+typedef int (*mms_io_tcp_connect_func)(void *data, const char *host, int port);
+
+typedef struct
+{
+ mms_io_select_func select;
+ void *select_data;
+ mms_io_read_func read;
+ void *read_data;
+ mms_io_write_func write;
+ void *write_data;
+ mms_io_tcp_connect_func connect;
+ void *connect_data;
+} mms_io_t;
+
+/* set default IO implementation, it will be used in absence of specific IO
+ parameter. Structure is referenced, not copied, must remain valid for entire
+ usage period. Passing NULL reverts to default, POSIX based implementation */
+void mms_set_default_io_impl(const mms_io_t *io);
+const mms_io_t* mms_get_default_io_impl();
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MMS_IO_H__ */
diff --git a/plugins/mms/libmms/mmsx.c b/plugins/mms/libmms/mmsx.c
new file mode 100644
index 00000000..fc95de86
--- /dev/null
+++ b/plugins/mms/libmms/mmsx.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl>
+ *
+ * This file is part of libmms a free mms protocol library
+ *
+ * libmms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libmss 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 Library 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
+ */
+
+/*
+ * mmsx is a small wrapper around the mms and mmsh protocol implementations
+ * in libmms. The mmsx functions provide transparent access to both protocols
+ * so that programs who wish to support both can do so with a single code path
+ * if desired.
+ */
+
+#include <stdlib.h>
+#include "mmsx.h"
+#include "mms.h"
+#include "mmsh.h"
+
+struct mmsx_s {
+ mms_t *connection;
+ mmsh_t *connection_h;
+};
+
+mmsx_t *mmsx_connect(mms_io_t *io, void *data, const char *url, int bandwidth)
+{
+ mmsx_t *mmsx = calloc(1, sizeof(mmsx_t));
+ char *try_mms_first = getenv("LIBMMS_TRY_MMS_FIRST");
+
+ if (!mmsx)
+ return mmsx;
+
+ /* Normally we try mmsh first, as mms: is a rollover protocol identifier
+ according to microsoft and recent mediaplayer versions will try
+ mmsh before mms for mms:// uris. Note that in case of a mmst:// or a
+ mmsh:// url the mms[h]_connect function will directly exit if it cannot
+ handle it. The LIBMMS_TRY_MMS_FIRST environment variable is there for
+ testing the mms code against servers which accept both mmsh and mms. */
+ if (try_mms_first, 1) {
+ mmsx->connection = mms_connect(io, data, url, bandwidth);
+ if (mmsx->connection)
+ return mmsx;
+ }
+
+ mmsx->connection_h = mmsh_connect(io, data, url, bandwidth);
+ if (mmsx->connection_h)
+ return mmsx;
+
+ if (!try_mms_first, 0) {
+ mmsx->connection = mms_connect(io, data, url, bandwidth);
+ if (mmsx->connection)
+ return mmsx;
+ }
+
+ free(mmsx);
+ return NULL;
+}
+
+int mmsx_read (mms_io_t *io, mmsx_t *mmsx, char *data, int len)
+{
+ if(mmsx->connection)
+ return mms_read(io, mmsx->connection, data, len);
+ else
+ return mmsh_read(io, mmsx->connection_h, data, len);
+}
+
+int mmsx_time_seek (mms_io_t *io, mmsx_t *mmsx, double time_sec)
+{
+ if(mmsx->connection)
+ return mms_time_seek(io, mmsx->connection, time_sec);
+ else
+ return mmsh_time_seek(io, mmsx->connection_h, time_sec);
+}
+
+mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *mmsx, mms_off_t offset, int origin)
+{
+ if(mmsx->connection)
+ return mms_seek(io, mmsx->connection, offset, origin);
+ else
+ return mmsh_seek(io, mmsx->connection_h, offset, origin);
+}
+
+double mmsx_get_time_length (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_time_length(mmsx->connection);
+ else
+ return mmsh_get_time_length(mmsx->connection_h);
+}
+
+uint64_t mmsx_get_raw_time_length (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_raw_time_length(mmsx->connection);
+ else
+ return mmsh_get_raw_time_length(mmsx->connection_h);
+}
+
+uint32_t mmsx_get_length (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_length(mmsx->connection);
+ else
+ return mmsh_get_length(mmsx->connection_h);
+}
+
+void mmsx_close (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ mms_close(mmsx->connection);
+ else
+ mmsh_close(mmsx->connection_h);
+
+ free(mmsx);
+}
+
+int mmsx_peek_header (mmsx_t *mmsx, char *data, int maxsize)
+{
+ if(mmsx->connection)
+ return mms_peek_header(mmsx->connection, data, maxsize);
+ else
+ return mmsh_peek_header(mmsx->connection_h, data, maxsize);
+}
+
+mms_off_t mmsx_get_current_pos (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_current_pos(mmsx->connection);
+ else
+ return mmsh_get_current_pos(mmsx->connection_h);
+}
+
+uint32_t mmsx_get_asf_header_len(mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_asf_header_len(mmsx->connection);
+ else
+ return mmsh_get_asf_header_len(mmsx->connection_h);
+}
+
+uint64_t mmsx_get_asf_packet_len (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_asf_packet_len(mmsx->connection);
+ else
+ return mmsh_get_asf_packet_len(mmsx->connection_h);
+}
+
+int mmsx_get_seekable (mmsx_t *mmsx)
+{
+ if(mmsx->connection)
+ return mms_get_seekable(mmsx->connection);
+ else
+ return mmsh_get_seekable(mmsx->connection_h);
+}
diff --git a/plugins/mms/libmms/mmsx.h b/plugins/mms/libmms/mmsx.h
new file mode 100644
index 00000000..df4adfce
--- /dev/null
+++ b/plugins/mms/libmms/mmsx.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl>
+ *
+ * This file is part of libmms a free mms protocol library
+ *
+ * libmms is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libmss 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 Library 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
+ *
+ * libmms public header
+ */
+
+/*
+ * mmsx is a small wrapper around the mms and mmsh protocol implementations
+ * in libmms. The mmsx functions provide transparent access to both protocols
+ * so that programs who wish to support both can do so with a single code path
+ * if desired.
+ */
+
+#ifndef HAVE_MMSX_H
+#define HAVE_MMSX_H
+
+#include <inttypes.h>
+#include "mmsio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct mmsx_s mmsx_t;
+
+mmsx_t* mmsx_connect (mms_io_t *io, void *data, const char *url, int bandwidth);
+
+int mmsx_read (mms_io_t *io, mmsx_t *instance, char *data, int len);
+int mmsx_time_seek (mms_io_t *io, mmsx_t *instance, double time_sec);
+mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *instance, mms_off_t offset, int origin);
+/* return total playback time in seconds */
+double mmsx_get_time_length (mmsx_t *instance);
+/* return raw total playback time in 100 nanosecs (10^-7) */
+uint64_t mmsx_get_raw_time_length (mmsx_t *instance);
+uint32_t mmsx_get_length (mmsx_t *instance);
+void mmsx_close (mmsx_t *instance);
+
+int mmsx_peek_header (mmsx_t *instance, char *data, int maxsize);
+
+mms_off_t mmsx_get_current_pos (mmsx_t *instance);
+
+uint32_t mmsx_get_asf_header_len (mmsx_t *instance);
+
+uint64_t mmsx_get_asf_packet_len (mmsx_t *instance);
+
+int mmsx_get_seekable (mmsx_t *instance);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
+
diff --git a/plugins/mms/libmms/uri.c b/plugins/mms/libmms/uri.c
new file mode 100644
index 00000000..6b6475eb
--- /dev/null
+++ b/plugins/mms/libmms/uri.c
@@ -0,0 +1,1033 @@
+/* GNet - Networking library
+ * Copyright (C) 2000-2003 David Helder, David Bolcsfoldi, Eric Williams
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* FIXME: #include "gnet-private.h" */
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "uri.h"
+#include <string.h>
+
+static void field_unescape (char *str);
+static char* field_escape (char* str, unsigned char mask);
+
+#define USERINFO_ESCAPE_MASK 0x01
+#define PATH_ESCAPE_MASK 0x02
+#define QUERY_ESCAPE_MASK 0x04
+#define FRAGMENT_ESCAPE_MASK 0x08
+
+/* #define FALSE 0 */
+/* #define TRUE (!FALSE) */
+
+static unsigned char neednt_escape_table[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x0c,
+ 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x0f,
+ 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+/*
+Perl code to generate above table:
+
+#!/usr/bin/perl
+
+$ok = "abcdefghijklmnopqrstuvwxyz" .
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" .
+ "0123456789" .
+ "-_.!~*'()";
+$userinfo_ok = ';:&=+\$,';
+$path_ok = ':\@&=+\$,;/';
+$query_ok = ';/?:\@&=+\$,';
+$fragment_ok = ';/?:\@&=+\$,';
+
+for ($i = 0; $i < 32; $i++)
+{
+ print " ";
+ for ($j = 0; $j < 8; $j++)
+ {
+ $num = 0;
+ $letter = chr(($i * 8) + $j);
+
+ $num |= 0b0001 if (index($userinfo_ok, $letter) != -1);
+ $num |= 0b0010 if (index($path_ok, $letter) != -1);
+ $num |= 0b0100 if (index($query_ok, $letter) != -1);
+ $num |= 0b1000 if (index($fragment_ok, $letter) != -1);
+ $num |= 0b1111 if (index($ok, $letter) != -1);
+
+ printf "0x%02x, ", $num;
+ }
+ print "\n";
+}
+*/
+
+
+/* our own ISSPACE. ANSI isspace is local dependent */
+#define ISSPACE(C) (((C) >= 9 && (C) <= 13) || (C) == ' ')
+
+
+static int split_user_passwd(const char* in, char** user, char** passwd)
+{
+ char *tmp = strdup(in);
+
+ if(!tmp)
+ return 0;
+ *passwd = strchr(tmp, ':');
+ if(!(*passwd))
+ {
+ free(tmp);
+ return 0;
+ }
+ *((*passwd)++) = '\0'; // don't you love C? :)
+
+ *user = strdup(tmp);
+ if(!*user)
+ return 0;
+ *passwd = strdup(*passwd);
+ if(!*passwd)
+ return 0;
+
+ free(tmp);
+ return 1;
+}
+
+/**
+ * gnet_uri_new
+ * @uri: URI string
+ *
+ * Creates a #GURI from a string. Empty fields are set to NULL. The
+ * parser does not validate the URI -- it will accept some malformed
+ * URI. URIs are usually in the form
+ * scheme://userinfo@hostname:port/path?query#fragment
+ *
+ * URIs created from user input are typically unescaped. URIs
+ * created from machine input (e.g. received over the internet) are
+ * typically escaped.
+ *
+ * Returns: a new #GURI, or NULL if there was a failure.
+ *
+ **/
+GURI*
+gnet_uri_new (const char* uri)
+{
+ GURI* guri = NULL;
+ const char* p;
+ const char* temp;
+
+ if (!uri) {
+ return NULL;
+ }
+
+ /* Skip initial whitespace */
+ p = uri;
+ while (*p && ISSPACE((int)*p))
+ ++p;
+ if (!*p) /* Error if it's just a string of space */
+ return NULL;
+
+ guri = malloc (sizeof (GURI));
+ memset (guri, 0, sizeof (GURI));
+
+ /* Scheme */
+ temp = p;
+ while (*p && *p != ':' && *p != '/' && *p != '?' && *p != '#')
+ ++p;
+ if (*p == ':')
+ {
+ guri->scheme = strndup (temp, p - temp);
+ ++p;
+ }
+ else /* This char is NUL, /, ?, or # */
+ p = temp;
+
+ /* Authority */
+ if (*p == '/' && p[1] == '/')
+ {
+ char *userinfo;
+ p += 2;
+
+ /* Userinfo */
+ temp = p;
+ while (*p && *p != '@' && *p != '/' ) /* Look for @ or / */
+ ++p;
+ if (*p == '@') /* Found userinfo */
+ {
+ userinfo = strndup (temp, p - temp);
+ if(!split_user_passwd(userinfo, &guri->user, &guri->passwd))
+ {
+ free(userinfo);
+ goto error;
+ }
+ free(userinfo);
+ ++p;
+ }
+ else
+ p = temp;
+
+ /* Hostname */
+
+ /* Check for IPv6 canonical hostname in brackets */
+ if (*p == '[')
+ {
+ p++; /* Skip [ */
+ temp = p;
+ while (*p && *p != ']') ++p;
+ if ((p - temp) == 0)
+ goto error;
+ guri->hostname = strndup (temp, p - temp);
+ if (*p)
+ p++; /* Skip ] (if there) */
+ }
+ else
+ {
+ temp = p;
+ while (*p && *p != '/' && *p != '?' && *p != '#' && *p != ':') ++p;
+ if ((p - temp) == 0)
+ goto error;
+ guri->hostname = strndup (temp, p - temp);
+ }
+
+ /* Port */
+ if (*p == ':')
+ {
+ for (++p; isdigit((int)*p); ++p)
+ guri->port = guri->port * 10 + (*p - '0');
+ }
+
+ }
+
+ /* Path (we are liberal and won't check if it starts with /) */
+ temp = p;
+ while (*p && *p != '?' && *p != '#')
+ ++p;
+ if (p != temp)
+ guri->path = strndup(temp, p - temp);
+
+ /* Query */
+ if (*p == '?')
+ {
+ temp = p + 1;
+ while (*p && *p != '#')
+ ++p;
+ guri->query = strndup (temp, p - temp);
+ }
+
+ /* Fragment */
+ if (*p == '#')
+ {
+ ++p;
+ guri->fragment = strdup (p);
+ }
+
+ return guri;
+
+ error:
+ gnet_uri_delete (guri);
+ return NULL;
+}
+
+
+/**
+ * gnet_uri_new_fields
+ * @scheme: scheme
+ * @hostname: host name
+ * @port: port
+ * @path: path
+ *
+ * Creates a #GURI from the fields. This function uses the most
+ * common fields. Use gnet_uri_new_fields_all() to specify all
+ * fields.
+ *
+ * Returns: a new #GURI.
+ *
+ **/
+GURI*
+gnet_uri_new_fields (const char* scheme, const char* hostname,
+ const int port, const char* path)
+{
+ GURI* uri = NULL;
+
+ uri = malloc (sizeof (GURI));
+ memset (uri, 0, sizeof (GURI));
+ if (scheme) uri->scheme = strdup (scheme);
+ if (hostname) uri->hostname = strdup (hostname);
+ uri->port = port;
+ if (path) uri->path = strdup (path);
+
+ return uri;
+}
+
+
+/**
+ * gnet_uri_new_fields_all
+ * @scheme: scheme
+ * @userinfo: user info
+ * @hostname: host name
+ * @port: port
+ * @path: path
+ * @query: query
+ * @fragment: fragment
+ *
+ * Creates a #GURI from all fields.
+ *
+ * Returns: a new #GURI.
+ *
+ **/
+GURI*
+gnet_uri_new_fields_all (const char* scheme, const char* user,
+ const char* passwd, const char* hostname,
+ const int port, const char* path,
+ const char* query, const char* fragment)
+{
+ GURI* uri = NULL;
+
+ uri = malloc (sizeof (GURI));
+ memset (uri, 0, sizeof (GURI));
+ if (scheme) uri->scheme = strdup (scheme);
+ if (user) uri->user = strdup (user);
+ if (passwd) uri->passwd = strdup (passwd);
+ if (hostname) uri->hostname = strdup (hostname);
+ uri->port = port;
+ if (path) uri->path = strdup (path);
+ if (query) uri->query = strdup (query);
+ if (fragment) uri->fragment = strdup (fragment);
+
+ return uri;
+}
+
+
+/**
+ * gnet_uri_clone:
+ * @uri: a #GURI
+ *
+ * Copies a #GURI.
+ *
+ * Returns: a copy of @uri.
+ *
+ **/
+GURI*
+gnet_uri_clone (const GURI* uri)
+{
+ GURI* uri2;
+
+ if (!uri) {
+ return NULL;
+ }
+
+ uri2 = malloc (sizeof (GURI));
+ memset (uri2, 0, sizeof (GURI));
+ uri2->scheme = strdup (uri->scheme);
+ uri2->user = strdup (uri->user);
+ uri2->passwd = strdup (uri->passwd);
+ uri2->hostname = strdup (uri->hostname);
+ uri2->port = uri->port;
+ uri2->path = strdup (uri->path);
+ uri2->query = strdup (uri->query);
+ uri2->fragment = strdup (uri->fragment);
+
+ return uri2;
+}
+
+
+/**
+ * gnet_uri_delete:
+ * @uri: a #GURI
+ *
+ * Deletes a #GURI.
+ *
+ **/
+void
+gnet_uri_delete (GURI* uri)
+{
+ if (uri)
+ {
+ free (uri->scheme);
+ free (uri->user);
+ free (uri->passwd);
+ free (uri->hostname);
+ free (uri->path);
+ free (uri->query);
+ free (uri->fragment);
+ free (uri);
+ }
+}
+
+
+
+
+#define SAFESTRCMP(A,B) (((A)&&(B))?(strcmp((A),(B))):((A)||(B)))
+
+/**
+ * gnet_uri_equal
+ * @p1: a #GURI
+ * @p2: another #GURI
+ *
+ * Compares two #GURI's for equality.
+ *
+ * Returns: TRUE if they are equal; FALSE otherwise.
+ *
+ **/
+int
+gnet_uri_equal (const char * p1, const char * p2)
+{
+ const GURI* uri1 = (const GURI*) p1;
+ const GURI* uri2 = (const GURI*) p2;
+
+ if (!uri1) {
+ return 0;
+ }
+ if (!uri2) {
+ return 0;
+ }
+
+ if (uri1->port == uri2->port &&
+ !SAFESTRCMP(uri1->scheme, uri2->scheme) &&
+ !SAFESTRCMP(uri1->user, uri2->user) &&
+ !SAFESTRCMP(uri1->passwd, uri2->passwd) &&
+ !SAFESTRCMP(uri1->hostname, uri2->hostname) &&
+ !SAFESTRCMP(uri1->path, uri2->path) &&
+ !SAFESTRCMP(uri1->query, uri2->query) &&
+ !SAFESTRCMP(uri1->fragment, uri2->fragment))
+ return 1;
+
+ return 0;
+}
+
+
+/**
+ * gnet_uri_hash
+ * @p: a #GURI
+ *
+ * Creates a hash code for @p for use with GHashTable.
+ *
+ * Returns: hash code for @p.
+ *
+ **/
+#if 0
+unsigned int
+gnet_uri_hash (const char * p)
+{
+ const GURI* uri = (const GURI*) p;
+ unsigned int h = 0;
+
+ if (!uri) {
+ return 0;
+ }
+
+ if (uri->scheme) h = g_str_hash (uri->scheme);
+ if (uri->user) h ^= g_str_hash (uri->user);
+ if (uri->passwd) h ^= g_str_hash (uri->passwd);
+ if (uri->hostname) h ^= g_str_hash (uri->hostname);
+ h ^= uri->port;
+ if (uri->path) h ^= g_str_hash (uri->path);
+ if (uri->query) h ^= g_str_hash (uri->query);
+ if (uri->fragment) h ^= g_str_hash (uri->fragment);
+
+ return h;
+}
+#endif
+
+
+/**
+ * gnet_uri_escape
+ * @uri: a #GURI
+ *
+ * Escapes the fields in a #GURI. Network protocols use escaped
+ * URIs. People use unescaped URIs.
+ *
+ **/
+void
+gnet_uri_escape (GURI* uri)
+{
+ if (!uri) {
+ return;
+ }
+
+ uri->user = field_escape (uri->user, USERINFO_ESCAPE_MASK);
+ uri->passwd = field_escape (uri->passwd, USERINFO_ESCAPE_MASK);
+ uri->path = field_escape (uri->path, PATH_ESCAPE_MASK);
+ uri->query = field_escape (uri->query, QUERY_ESCAPE_MASK);
+ uri->fragment = field_escape (uri->fragment, FRAGMENT_ESCAPE_MASK);
+}
+
+
+/**
+ * gnet_uri_unescape
+ * @uri: a #GURI
+ *
+ * Unescapes the fields in the URI. Network protocols use escaped
+ * URIs. People use unescaped URIs.
+ *
+ **/
+void
+gnet_uri_unescape (GURI* uri)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->user)
+ field_unescape (uri->user);
+ if (uri->passwd)
+ field_unescape (uri->passwd);
+ if (uri->path)
+ field_unescape (uri->path);
+ if (uri->query)
+ field_unescape (uri->query);
+ if (uri->fragment)
+ field_unescape (uri->fragment);
+}
+
+
+static char*
+field_escape (char* str, unsigned char mask)
+{
+ int len;
+ int i;
+ int must_escape = 0;
+ char* dst;
+ int j;
+
+ if (str == NULL)
+ return NULL;
+
+ /* Roughly calculate buffer size */
+ len = 0;
+ for (i = 0; str[i]; i++)
+ {
+ if (neednt_escape_table[(unsigned int) str[i]] & mask)
+ len++;
+ else
+ {
+ len += 3;
+ must_escape = 1;
+ }
+ }
+
+ /* Don't escape if unnecessary */
+ if (must_escape == 0)
+ return str;
+
+ /* Allocate buffer */
+ dst = (char*) malloc(len + 1);
+ memset (dst, 0, len+1);
+
+ /* Copy */
+ for (i = j = 0; str[i]; i++, j++)
+ {
+ /* Unescaped character */
+ if (neednt_escape_table[(unsigned int) str[i]] & mask)
+ {
+ dst[j] = str[i];
+ }
+
+ /* Escaped character */
+ else
+ {
+ dst[j] = '%';
+
+ if (((str[i] & 0xf0) >> 4) < 10)
+ dst[j+1] = ((str[i] & 0xf0) >> 4) + '0';
+ else
+ dst[j+1] = ((str[i] & 0xf0) >> 4) + 'a' - 10;
+
+ if ((str[i] & 0x0f) < 10)
+ dst[j+2] = (str[i] & 0x0f) + '0';
+ else
+ dst[j+2] = (str[i] & 0x0f) + 'a' - 10;
+
+ j += 2; /* and j is incremented in loop too */
+ }
+ }
+ dst[j] = '\0';
+
+ free (str);
+ return dst;
+}
+
+
+
+static void
+field_unescape (char* s)
+{
+ char* src;
+ char* dst;
+
+ for (src = dst = s; *src; ++src, ++dst)
+ {
+ if (src[0] == '%' && src[1] != '\0' && src[2] != '\0')
+ {
+ int high, low;
+
+ if ('a' <= src[1] && src[1] <= 'f')
+ high = src[1] - 'a' + 10;
+ else if ('A' <= src[1] && src[1] <= 'F')
+ high = src[1] - 'A' + 10;
+ else if ('0' <= src[1] && src[1] <= '9')
+ high = src[1] - '0';
+ else /* malformed */
+ goto regular_copy;
+
+ if ('a' <= src[2] && src[2] <= 'f')
+ low = src[2] - 'a' + 10;
+ else if ('A' <= src[2] && src[2] <= 'F')
+ low = src[2] - 'A' + 10;
+ else if ('0' <= src[2] && src[2] <= '9')
+ low = src[2] - '0';
+ else /* malformed */
+ goto regular_copy;
+
+ *dst = (char)((high << 4) + low);
+ src += 2;
+ }
+ else
+ {
+ regular_copy:
+ *dst = *src;
+ }
+ }
+
+ *dst = '\0';
+}
+
+
+
+/**
+ * gnet_uri_get_string
+ * @uri: a #GURI
+ *
+ * Gets a string representation of a #GURI. This function does not
+ * escape or unescape the fields first. Call gnet_uri_escape() or
+ * gnet_uri_unescape first if necessary.
+ *
+ * Returns: a string.
+ *
+ **/
+char*
+gnet_uri_get_string (const GURI* uri)
+{
+ char* rv = NULL;
+ char *buffer = malloc (1024);
+ memset (buffer, 0, 1024);
+ char *b = buffer;
+ int remaining = 1024;
+
+ if (!uri) {
+ return NULL;
+ }
+
+ if (uri->scheme) {
+ int n = snprintf (buffer, n, "%s:", uri->scheme);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ if (uri->user || uri->passwd || uri->hostname || uri->port) {
+ strcpy (buffer, "//");
+ buffer += 2;
+ remaining -= 2;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ if (uri->user)
+ {
+ int n = strlen (uri->user);
+ memcpy (buffer, uri->user, n+1);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ strcpy (buffer, "@");
+ buffer += 1;
+ remaining -= 1;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+ if (uri->passwd)
+ {
+ int n = strlen (uri->passwd);
+ memcpy (buffer, uri->passwd, n+1);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ strcpy (buffer, "@");
+ buffer += 1;
+ remaining -= 1;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ /* Add brackets around the hostname if it's IPv6 */
+ if (uri->hostname)
+ {
+ if (strchr(uri->hostname, ':') == NULL) {
+ int n = strlen (uri->hostname);
+ memcpy (buffer, uri->hostname, n+1);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+ else {
+ int n = snprintf (buffer, remaining, "[%s]", uri->hostname);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+ }
+
+ if (uri->port) {
+ int n = snprintf (buffer, remaining, ":%d", uri->port);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ if (uri->path)
+ {
+ if (*uri->path == '/' ||
+ !(uri->user || uri->passwd || uri->hostname || uri->port)) {
+ int n = strlen (uri->path);
+ memcpy (buffer, uri->path, n+1);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+ else {
+ int n = snprintf (buffer, remaining, "/%s", uri->path);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+ }
+
+ if (uri->query) {
+ int n = snprintf (buffer, remaining, "?%s", uri->query);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ if (uri->fragment) {
+ int n = snprintf (buffer, remaining, "#%s", uri->fragment);
+ buffer += n;
+ remaining -= n;
+ if (remaining < 10) {
+ free (buffer);
+ return NULL;
+ }
+ }
+
+ /* Free only GString not data contained, return the data instead */
+ return b;
+}
+
+
+/**
+ * gnet_uri_set_scheme
+ * @uri: a #GURI
+ * @scheme: scheme
+ *
+ * Sets a #GURI's scheme.
+ *
+ **/
+void
+gnet_uri_set_scheme (GURI* uri, const char* scheme)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->scheme)
+ {
+ free (uri->scheme);
+ uri->scheme = NULL;
+ }
+
+ if (scheme)
+ uri->scheme = strdup (scheme);
+}
+
+
+/**
+ * gnet_uri_set_userinfo
+ * @uri: a #GURI
+ * @userinfo: user info
+ *
+ * Sets a #GURI's user info.
+ *
+ **/
+void
+gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->user)
+ {
+ free (uri->user);
+ uri->user = NULL;
+ }
+ if (uri->passwd)
+ {
+ free (uri->passwd);
+ uri->passwd = NULL;
+ }
+
+ if (user)
+ uri->user = strdup (user);
+ if (passwd)
+ uri->passwd = strdup (passwd);
+}
+
+
+/**
+ * gnet_uri_set_hostname
+ * @uri: a #GURI
+ * @hostname: host name
+ *
+ * Sets a #GURI's host name.
+ *
+ **/
+void
+gnet_uri_set_hostname (GURI* uri, const char* hostname)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->hostname)
+ {
+ free (uri->hostname);
+ uri->hostname = NULL;
+ }
+
+ if (hostname)
+ uri->hostname = strdup (hostname);
+}
+
+
+/**
+ * gnet_uri_set_port
+ * @uri: a #GURI
+ * @port: port
+ *
+ * Set a #GURI's port.
+ *
+ **/
+void
+gnet_uri_set_port (GURI* uri, int port)
+{
+ uri->port = port;
+}
+
+
+/**
+ * gnet_uri_set_path
+ * @uri: a #GURI
+ * @path: path
+ *
+ * Set a #GURI's path.
+ *
+ **/
+void
+gnet_uri_set_path (GURI* uri, const char* path)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->path)
+ {
+ free (uri->path);
+ uri->path = NULL;
+ }
+
+ if (path)
+ uri->path = strdup (path);
+}
+
+
+
+/**
+ * gnet_uri_set_query
+ * @uri: a #GURI
+ * @query: query
+ *
+ * Set a #GURI's query.
+ *
+ **/
+void
+gnet_uri_set_query (GURI* uri, const char* query)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->query)
+ {
+ free (uri->query);
+ uri->query = NULL;
+ }
+
+ if (query)
+ uri->query = strdup (query);
+}
+
+
+/**
+ * gnet_uri_set_fragment
+ * @uri: a #GURI
+ * @fragment: fragment
+ *
+ * Set a #GURI's fragment.
+ *
+ **/
+void
+gnet_uri_set_fragment (GURI* uri, const char* fragment)
+{
+ if (!uri) {
+ return;
+ }
+
+ if (uri->fragment)
+ {
+ free (uri->fragment);
+ uri->fragment = NULL;
+ }
+
+ if (fragment)
+ uri->fragment = strdup (fragment);
+}
+
+
+/**
+ * gnet_mms_helper
+ * @uri: a #GURI
+ *
+ * returns char* representation of an uri that is sutable for
+ * using in mms protocol.
+ * '/path?query'
+ *
+ **/
+
+char* gnet_mms_helper(const GURI* uri, int make_absolute)
+{
+ size_t len = 0;
+ char *ret, *tmp = NULL;
+
+
+ /* Strip leading slashes and calculate the length of the path
+ * which might not be present in the URI */
+ if (uri->path) {
+ tmp = uri->path;
+ while (*tmp == '/')
+ ++tmp;
+ len += strlen(tmp);
+ }
+ /* Append length of the query part */
+ if (uri->query)
+ len += strlen(uri->query) + 1; /* + '?' */
+
+ if (!(ret = (char *) malloc(len + 2)))
+ return NULL;
+ memset (ret, 0, len + 2);
+
+ if (make_absolute)
+ strcpy(ret, "/");
+ else
+ ret[0] = 0;
+
+ /* Copy the optional path */
+ if (tmp)
+ strcat(ret, tmp);
+
+ /* Copy the optional query */
+ if (uri->query) {
+ strcat(ret, "?");
+ strcat(ret, uri->query);
+ }
+
+ return ret;
+}
diff --git a/plugins/mms/libmms/uri.h b/plugins/mms/libmms/uri.h
new file mode 100644
index 00000000..75c96779
--- /dev/null
+++ b/plugins/mms/libmms/uri.h
@@ -0,0 +1,92 @@
+/* GNet - Networking library
+ * Copyright (C) 2000-2001 David Helder, David Bolcsfoldi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef _GNET_URI_H
+#define _GNET_URI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * GURI:
+ * @scheme: Scheme (or protocol)
+ * @userinfo: User info
+ * @hostname: Host name
+ * @port: Port number
+ * @path: Path
+ * @query: Query
+ * @fragment: Fragment
+ *
+ * The #GURI structure represents a URI. All fields in this
+ * structure are publicly readable.
+ *
+ **/
+typedef struct _GURI GURI;
+
+struct _GURI
+{
+ char* scheme;
+ char* user;
+ char* passwd;
+ char* hostname;
+ int port;
+ char* path;
+ char* query;
+ char* fragment;
+};
+
+
+
+GURI* gnet_uri_new (const char* uri);
+GURI* gnet_uri_new_fields (const char* scheme, const char* hostname,
+ const int port, const char* path);
+GURI*
+gnet_uri_new_fields_all (const char* scheme, const char* user,
+ const char* passwd, const char* hostname,
+ const int port, const char* path,
+ const char* query, const char* fragment);
+GURI* gnet_uri_clone (const GURI* uri);
+void gnet_uri_delete (GURI* uri);
+
+int gnet_uri_equal (const char * p1, const char * p2);
+unsigned int gnet_uri_hash (const char * p);
+
+void gnet_uri_escape (GURI* uri);
+void gnet_uri_unescape (GURI* uri);
+
+char* gnet_uri_get_string (const GURI* uri);
+
+void gnet_uri_set_scheme (GURI* uri, const char* scheme);
+void gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd);
+void gnet_uri_set_hostname (GURI* uri, const char* hostname);
+void gnet_uri_set_port (GURI* uri, int port);
+void gnet_uri_set_path (GURI* uri, const char* path);
+void gnet_uri_set_query (GURI* uri, const char* query);
+void gnet_uri_set_fragment (GURI* uri, const char* fragment);
+
+char* gnet_mms_helper(const GURI* uri, int make_absolute);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _GNET_URI_H */
diff --git a/plugins/mms/mms.c b/plugins/mms/mmsplug.c
index 4ad57832..4ad57832 100644
--- a/plugins/mms/mms.c
+++ b/plugins/mms/mmsplug.c
diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c
index 098d672a..13544238 100644
--- a/plugins/mpgmad/mpgmad.c
+++ b/plugins/mpgmad/mpgmad.c
@@ -372,108 +372,110 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
if (sample <= 0 && !got_xing_header)
{
size_t framepos = deadbeef->ftell (buffer->file);
-// trace ("trying to read xing header at pos %d\n", framepos);
- if (ver == 1) {
- deadbeef->fseek (buffer->file, 32, SEEK_CUR);
- }
- else {
- deadbeef->fseek (buffer->file, 17, SEEK_CUR);
- }
- const char xing[] = "Xing";
- const char info[] = "Info";
- char magic[4];
- if (deadbeef->fread (magic, 1, 4, buffer->file) != 4) {
- trace ("cmp3_scan_stream: EOF while checking for Xing header\n");
- return -1; // EOF
- }
-
-// trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]);
-
- if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) {
- trace ("xing/info frame found\n");
- buffer->startoffset += packetlength;
- // read flags
- uint32_t flags;
- uint8_t buf[4];
- if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
- trace ("cmp3_scan_stream: EOF while parsing Xing header\n");
+ if (!buffer->file->vfs->streaming) {
+ // trace ("trying to read xing header at pos %d\n", framepos);
+ if (ver == 1) {
+ deadbeef->fseek (buffer->file, 32, SEEK_CUR);
+ }
+ else {
+ deadbeef->fseek (buffer->file, 17, SEEK_CUR);
+ }
+ const char xing[] = "Xing";
+ const char info[] = "Info";
+ char magic[4];
+ if (deadbeef->fread (magic, 1, 4, buffer->file) != 4) {
+ trace ("cmp3_scan_stream: EOF while checking for Xing header\n");
return -1; // EOF
}
- flags = extract_i32 (buf);
- if (flags & FRAMES_FLAG) {
- // read number of frames
+
+ // trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]);
+
+ if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) {
+ trace ("xing/info frame found\n");
+ buffer->startoffset += packetlength;
+ // read flags
+ uint32_t flags;
+ uint8_t buf[4];
if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
trace ("cmp3_scan_stream: EOF while parsing Xing header\n");
return -1; // EOF
}
- uint32_t nframes = extract_i32 (buf);
- buffer->duration = (float)nframes * (float)samples_per_frame / (float)samplerate;
- trace ("xing totalsamples: %d, nframes: %d, samples_per_frame: %d\n", nframes*samples_per_frame, nframes, samples_per_frame);
- if (nframes <= 0 || samples_per_frame <= 0) {
- trace ("bad xing header\n");
- continue;
+ flags = extract_i32 (buf);
+ if (flags & FRAMES_FLAG) {
+ // read number of frames
+ if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
+ trace ("cmp3_scan_stream: EOF while parsing Xing header\n");
+ return -1; // EOF
+ }
+ uint32_t nframes = extract_i32 (buf);
+ buffer->duration = (float)nframes * (float)samples_per_frame / (float)samplerate;
+ trace ("xing totalsamples: %d, nframes: %d, samples_per_frame: %d\n", nframes*samples_per_frame, nframes, samples_per_frame);
+ if (nframes <= 0 || samples_per_frame <= 0) {
+ trace ("bad xing header\n");
+ continue;
+ }
+ buffer->totalsamples = nframes * samples_per_frame;
+ buffer->samplerate = samplerate;
+ }
+ if (flags & BYTES_FLAG) {
+ deadbeef->fseek (buffer->file, 4, SEEK_CUR);
+ }
+ if (flags & TOC_FLAG) {
+ deadbeef->fseek (buffer->file, 100, SEEK_CUR);
+ }
+ if (flags & VBR_SCALE_FLAG) {
+ deadbeef->fseek (buffer->file, 4, SEEK_CUR);
+ }
+ // lame header
+ if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
+ trace ("cmp3_scan_stream: EOF while reading LAME header\n");
+ return -1; // EOF
+ }
+ // trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
+ if (!memcmp (buf, "LAME", 4)) {
+ trace ("lame header found\n");
+ deadbeef->fseek (buffer->file, 6, SEEK_CUR);
+
+ // FIXME: that can be optimized by single read
+ uint8_t lpf;
+ deadbeef->fread (&lpf, 1, 1, buffer->file);
+ //3 floats: replay gain
+ deadbeef->fread (buf, 1, 4, buffer->file);
+ // float rg_peaksignalamp = extract_f32 (buf);
+ deadbeef->fread (buf, 1, 2, buffer->file);
+ // uint16_t rg_radio = extract_i16 (buf);
+
+ deadbeef->fread (buf, 1, 2, buffer->file);
+ // uint16_t rg_audiophile = extract_i16 (buf);
+
+ // skip
+ deadbeef->fseek (buffer->file, 2, SEEK_CUR);
+ deadbeef->fread (buf, 1, 3, buffer->file);
+ uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4);
+ uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]);
+ // skip
+ deadbeef->fseek (buffer->file, 1, SEEK_CUR);
+ // mp3gain
+ uint8_t mp3gain;
+ deadbeef->fread (&mp3gain, 1, 1, buffer->file);
+ // skip
+ deadbeef->fseek (buffer->file, 2, SEEK_CUR);
+ // musiclen
+ deadbeef->fread (buf, 1, 4, buffer->file);
+ // uint32_t musiclen = extract_i32 (buf);
+
+ //trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen);
+ // skip crc
+ //deadbeef->fseek (buffer->file, 4, SEEK_CUR);
+ buffer->startdelay = startdelay;
+ buffer->enddelay = enddelay;
+ trace ("lame totalsamples: %d\n", buffer->totalsamples);
+ }
+ if (sample <= 0 && (flags&FRAMES_FLAG)) {
+ buffer->totalsamples -= buffer->enddelay;
+ deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ return 0;
}
- buffer->totalsamples = nframes * samples_per_frame;
- buffer->samplerate = samplerate;
- }
- if (flags & BYTES_FLAG) {
- deadbeef->fseek (buffer->file, 4, SEEK_CUR);
- }
- if (flags & TOC_FLAG) {
- deadbeef->fseek (buffer->file, 100, SEEK_CUR);
- }
- if (flags & VBR_SCALE_FLAG) {
- deadbeef->fseek (buffer->file, 4, SEEK_CUR);
- }
- // lame header
- if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
- trace ("cmp3_scan_stream: EOF while reading LAME header\n");
- return -1; // EOF
- }
-// trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
- if (!memcmp (buf, "LAME", 4)) {
- trace ("lame header found\n");
- deadbeef->fseek (buffer->file, 6, SEEK_CUR);
-
- // FIXME: that can be optimized by single read
- uint8_t lpf;
- deadbeef->fread (&lpf, 1, 1, buffer->file);
- //3 floats: replay gain
- deadbeef->fread (buf, 1, 4, buffer->file);
- // float rg_peaksignalamp = extract_f32 (buf);
- deadbeef->fread (buf, 1, 2, buffer->file);
- // uint16_t rg_radio = extract_i16 (buf);
-
- deadbeef->fread (buf, 1, 2, buffer->file);
- // uint16_t rg_audiophile = extract_i16 (buf);
-
- // skip
- deadbeef->fseek (buffer->file, 2, SEEK_CUR);
- deadbeef->fread (buf, 1, 3, buffer->file);
- uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4);
- uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]);
- // skip
- deadbeef->fseek (buffer->file, 1, SEEK_CUR);
- // mp3gain
- uint8_t mp3gain;
- deadbeef->fread (&mp3gain, 1, 1, buffer->file);
- // skip
- deadbeef->fseek (buffer->file, 2, SEEK_CUR);
- // musiclen
- deadbeef->fread (buf, 1, 4, buffer->file);
-// uint32_t musiclen = extract_i32 (buf);
-
- //trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen);
- // skip crc
- //deadbeef->fseek (buffer->file, 4, SEEK_CUR);
- buffer->startdelay = startdelay;
- buffer->enddelay = enddelay;
- trace ("lame totalsamples: %d\n", buffer->totalsamples);
- }
- if (sample <= 0 && (flags&FRAMES_FLAG)) {
- buffer->totalsamples -= buffer->enddelay;
- deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
- return 0;
}
}
if (sample == 0) {
diff --git a/plugins/oss/oss.c b/plugins/oss/oss.c
index 6c1045bf..52c417fa 100644
--- a/plugins/oss/oss.c
+++ b/plugins/oss/oss.c
@@ -130,6 +130,7 @@ oss_init (void) {
static int
oss_change_rate (int rate) {
if (!fd) {
+ oss_rate = rate;
return oss_rate;
}
if (rate == oss_rate) {
@@ -161,6 +162,7 @@ oss_free (void) {
oss_terminate = 0;
if (fd) {
close (fd);
+ fd = 0;
}
if (mutex) {
deadbeef->mutex_free (mutex);
diff --git a/plugins/tta/ttaplug.c b/plugins/tta/ttaplug.c
index f220d151..196df859 100644
--- a/plugins/tta/ttaplug.c
+++ b/plugins/tta/ttaplug.c
@@ -166,7 +166,7 @@ tta_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) {
if (size > 0 && !info->remaining) {
info->remaining = get_samples (&info->tta, info->buffer);
- if (!info->remaining) {
+ if (info->remaining <= 0) {
break;
}
}
diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c
index e73741a2..048f964e 100644
--- a/plugins/vfs_curl/vfs_curl.c
+++ b/plugins/vfs_curl/vfs_curl.c
@@ -391,6 +391,11 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream)
uint8_t key[256];
uint8_t value[256];
int refresh_playlist = 0;
+
+ if (fp->length == 0) {
+ fp->length = -1;
+ }
+
while (p < end) {
if (p <= end - 4) {
if (!memcmp (p, "\r\n\r\n", 4)) {
@@ -482,7 +487,7 @@ http_thread_func (void *ctx) {
int status;
- trace ("vfs_curl: started loading data\n");
+ trace ("vfs_curl: started loading data %s\n", fp->url);
for (;;) {
struct curl_slist *headers = NULL;
curl_easy_reset (curl);
@@ -495,6 +500,7 @@ http_thread_func (void *ctx) {
curl_easy_setopt (curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, http_content_header_handler);
curl_easy_setopt (curl, CURLOPT_HEADERDATA, ctx);
+ curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, http_curl_control);
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, ctx);
@@ -827,6 +833,7 @@ http_getlength (DB_FILE *stream) {
assert (stream);
HTTP_FILE *fp = (HTTP_FILE *)stream;
if (fp->status == STATUS_ABORTED) {
+ trace ("length: -1\n");
return -1;
}
if (!fp->tid) {
@@ -835,7 +842,7 @@ http_getlength (DB_FILE *stream) {
while (fp->status == STATUS_INITIAL) {
usleep (3000);
}
- //trace ("length: %d\n", fp->length);
+ trace ("length: %d\n", fp->length);
return fp->length;
}