• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *
3  * GStreamer
4  * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include <gst/gst.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include "gstmms.h"
30 
31 #define DEFAULT_CONNECTION_SPEED    0
32 
33 enum
34 {
35   PROP_0,
36   PROP_LOCATION,
37   PROP_CONNECTION_SPEED
38 };
39 
40 
41 GST_DEBUG_CATEGORY_STATIC (mmssrc_debug);
42 #define GST_CAT_DEFAULT mmssrc_debug
43 
44 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
45     GST_PAD_SRC,
46     GST_PAD_ALWAYS,
47     GST_STATIC_CAPS ("video/x-ms-asf")
48     );
49 
50 static void gst_mms_finalize (GObject * gobject);
51 static void gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data);
52 
53 static void gst_mms_set_property (GObject * object, guint prop_id,
54     const GValue * value, GParamSpec * pspec);
55 static void gst_mms_get_property (GObject * object, guint prop_id,
56     GValue * value, GParamSpec * pspec);
57 
58 static gboolean gst_mms_query (GstBaseSrc * src, GstQuery * query);
59 
60 static gboolean gst_mms_start (GstBaseSrc * bsrc);
61 static gboolean gst_mms_stop (GstBaseSrc * bsrc);
62 static gboolean gst_mms_is_seekable (GstBaseSrc * src);
63 static gboolean gst_mms_get_size (GstBaseSrc * src, guint64 * size);
64 static gboolean gst_mms_prepare_seek_segment (GstBaseSrc * src,
65     GstEvent * event, GstSegment * segment);
66 static gboolean gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment);
67 
68 static GstFlowReturn gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf);
69 
70 static gboolean gst_mms_uri_set_uri (GstURIHandler * handler,
71     const gchar * uri, GError ** error);
72 
73 #define gst_mms_parent_class parent_class
74 G_DEFINE_TYPE_WITH_CODE (GstMMS, gst_mms, GST_TYPE_PUSH_SRC,
75     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_mms_uri_handler_init));
76 
77 /* initialize the plugin's class */
78 static void
gst_mms_class_init(GstMMSClass * klass)79 gst_mms_class_init (GstMMSClass * klass)
80 {
81   GObjectClass *gobject_class = (GObjectClass *) klass;
82   GstElementClass *gstelement_class = (GstElementClass *) klass;
83   GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
84   GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
85 
86   gobject_class->set_property = gst_mms_set_property;
87   gobject_class->get_property = gst_mms_get_property;
88   gobject_class->finalize = gst_mms_finalize;
89 
90   g_object_class_install_property (gobject_class, PROP_LOCATION,
91       g_param_spec_string ("location", "location",
92           "Host URL to connect to. Accepted are mms://, mmsu://, mmst:// URL types",
93           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
94 
95   /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms
96    * uses int for it */
97   g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
98       g_param_spec_uint64 ("connection-speed", "Connection Speed",
99           "Network connection speed in kbps (0 = unknown)",
100           0, G_MAXINT / 1000, DEFAULT_CONNECTION_SPEED,
101           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
102 
103   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
104 
105   gst_element_class_set_static_metadata (gstelement_class,
106       "MMS streaming source", "Source/Network",
107       "Receive data streamed via MSFT Multi Media Server protocol",
108       "Maciej Katafiasz <mathrick@users.sourceforge.net>");
109 
110   GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
111 
112   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
113   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
114 
115   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
116 
117   gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_mms_is_seekable);
118   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mms_get_size);
119   gstbasesrc_class->prepare_seek_segment =
120       GST_DEBUG_FUNCPTR (gst_mms_prepare_seek_segment);
121   gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mms_do_seek);
122   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_mms_query);
123 }
124 
125 /* initialize the new element
126  * instantiate pads and add them to element
127  * set functions
128  * initialize structure
129  */
130 static void
gst_mms_init(GstMMS * mmssrc)131 gst_mms_init (GstMMS * mmssrc)
132 {
133   mmssrc->uri_name = NULL;
134   mmssrc->current_connection_uri_name = NULL;
135   mmssrc->connection = NULL;
136   mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
137 }
138 
139 static void
gst_mms_finalize(GObject * gobject)140 gst_mms_finalize (GObject * gobject)
141 {
142   GstMMS *mmssrc = GST_MMS (gobject);
143 
144   /* We may still have a connection open, as we preserve unused / pristine
145      open connections in stop to reuse them in start. */
146   if (mmssrc->connection) {
147     mmsx_close (mmssrc->connection);
148     mmssrc->connection = NULL;
149   }
150 
151   if (mmssrc->current_connection_uri_name) {
152     g_free (mmssrc->current_connection_uri_name);
153     mmssrc->current_connection_uri_name = NULL;
154   }
155 
156   if (mmssrc->uri_name) {
157     g_free (mmssrc->uri_name);
158     mmssrc->uri_name = NULL;
159   }
160 
161   G_OBJECT_CLASS (parent_class)->finalize (gobject);
162 }
163 
164 /* FIXME operating in TIME rather than BYTES could remove this altogether
165  * and be more convenient elsewhere */
166 static gboolean
gst_mms_query(GstBaseSrc * src,GstQuery * query)167 gst_mms_query (GstBaseSrc * src, GstQuery * query)
168 {
169   GstMMS *mmssrc = GST_MMS (src);
170   gboolean res = TRUE;
171   GstFormat format;
172   gint64 value;
173 
174   switch (GST_QUERY_TYPE (query)) {
175     case GST_QUERY_POSITION:
176       gst_query_parse_position (query, &format, &value);
177       if (format != GST_FORMAT_BYTES) {
178         res = FALSE;
179         break;
180       }
181       value = (gint64) mmsx_get_current_pos (mmssrc->connection);
182       gst_query_set_position (query, format, value);
183       break;
184     case GST_QUERY_DURATION:
185       if (!mmsx_get_seekable (mmssrc->connection)) {
186         res = FALSE;
187         break;
188       }
189       gst_query_parse_duration (query, &format, &value);
190       switch (format) {
191         case GST_FORMAT_BYTES:
192           value = (gint64) mmsx_get_length (mmssrc->connection);
193           gst_query_set_duration (query, format, value);
194           break;
195         case GST_FORMAT_TIME:
196           value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
197           gst_query_set_duration (query, format, value);
198           break;
199         default:
200           res = FALSE;
201       }
202       break;
203     default:
204       /* chain to parent */
205       res =
206           GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
207       break;
208   }
209 
210   return res;
211 }
212 
213 
214 static gboolean
gst_mms_prepare_seek_segment(GstBaseSrc * src,GstEvent * event,GstSegment * segment)215 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
216     GstSegment * segment)
217 {
218   GstSeekType cur_type, stop_type;
219   gint64 cur, stop;
220   GstSeekFlags flags;
221   GstFormat seek_format;
222   gdouble rate;
223 
224   gst_event_parse_seek (event, &rate, &seek_format, &flags,
225       &cur_type, &cur, &stop_type, &stop);
226 
227   if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
228     GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
229     return FALSE;
230   }
231 
232   if (stop_type != GST_SEEK_TYPE_NONE) {
233     GST_LOG_OBJECT (src, "Stop seeking not supported");
234     return FALSE;
235   }
236 
237   if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
238     GST_LOG_OBJECT (src, "Only absolute seeking is supported");
239     return FALSE;
240   }
241 
242   /* We would like to convert from GST_FORMAT_TIME to GST_FORMAT_BYTES here
243      when needed, but we cannot as to do that we need to actually do the seek,
244      so we handle this in do_seek instead. */
245 
246   /* FIXME implement relative seeking, we could do any needed relevant
247      seeking calculations here (in seek_format metrics), before the re-init
248      of the segment. */
249 
250   gst_segment_init (segment, seek_format);
251   gst_segment_do_seek (segment, rate, seek_format, flags, cur_type, cur,
252       stop_type, stop, NULL);
253 
254   return TRUE;
255 }
256 
257 static gboolean
gst_mms_do_seek(GstBaseSrc * src,GstSegment * segment)258 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
259 {
260   gint64 start;
261   GstMMS *mmssrc = GST_MMS (src);
262 
263   if (segment->format == GST_FORMAT_TIME) {
264     if (!mmsx_time_seek (NULL, mmssrc->connection,
265             (double) segment->start / GST_SECOND)) {
266       GST_LOG_OBJECT (mmssrc, "mmsx_time_seek() failed");
267       return FALSE;
268     }
269     start = mmsx_get_current_pos (mmssrc->connection);
270     GST_INFO_OBJECT (mmssrc, "sought to %" GST_TIME_FORMAT ", offset after "
271         "seek: %" G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), start);
272   } else if (segment->format == GST_FORMAT_BYTES) {
273     start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
274     /* mmsx_seek will close and reopen the connection when seeking with the
275        mmsh protocol, if the reopening fails this is indicated with -1 */
276     if (start == -1) {
277       GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
278       return FALSE;
279     }
280     GST_INFO_OBJECT (mmssrc, "sought to: %" G_GINT64_FORMAT " bytes, "
281         "result: %" G_GINT64_FORMAT, segment->start, start);
282   } else {
283     GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
284         GST_STR_NULL (gst_format_get_name (segment->format)));
285     return FALSE;
286   }
287   gst_segment_init (segment, GST_FORMAT_BYTES);
288   gst_segment_do_seek (segment, segment->rate, GST_FORMAT_BYTES,
289       GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
290       segment->stop, NULL);
291   return TRUE;
292 }
293 
294 
295 /* get function
296  * this function generates new data when needed
297  */
298 
299 
300 static GstFlowReturn
gst_mms_create(GstPushSrc * psrc,GstBuffer ** buf)301 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
302 {
303   GstMMS *mmssrc = GST_MMS (psrc);
304   guint8 *data;
305   guint blocksize;
306   gint result;
307   goffset offset;
308 
309   *buf = NULL;
310 
311   offset = mmsx_get_current_pos (mmssrc->connection);
312 
313   /* Check if a seek perhaps has wrecked our connection */
314   if (offset == -1) {
315     GST_ERROR_OBJECT (mmssrc,
316         "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
317     return GST_FLOW_ERROR;
318   }
319 
320   /* Choose blocksize best for optimum performance */
321   if (offset == 0)
322     blocksize = mmsx_get_asf_header_len (mmssrc->connection);
323   else
324     blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
325 
326   data = g_try_malloc (blocksize);
327   if (!data) {
328     GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
329     return GST_FLOW_ERROR;
330   }
331 
332   GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
333   result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
334   /* EOS? */
335   if (result == 0)
336     goto eos;
337 
338   *buf = gst_buffer_new_wrapped (data, result);
339   GST_BUFFER_OFFSET (*buf) = offset;
340 
341   GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
342       " and size %u", offset, result);
343 
344   return GST_FLOW_OK;
345 
346 eos:
347   {
348     GST_DEBUG_OBJECT (mmssrc, "EOS");
349     g_free (data);
350     *buf = NULL;
351     return GST_FLOW_EOS;
352   }
353 }
354 
355 static gboolean
gst_mms_is_seekable(GstBaseSrc * src)356 gst_mms_is_seekable (GstBaseSrc * src)
357 {
358   GstMMS *mmssrc = GST_MMS (src);
359 
360   return mmsx_get_seekable (mmssrc->connection);
361 }
362 
363 static gboolean
gst_mms_get_size(GstBaseSrc * src,guint64 * size)364 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
365 {
366   GstMMS *mmssrc = GST_MMS (src);
367 
368   /* non seekable usually means live streams, and get_length() returns,
369      erm, interesting values for live streams */
370   if (!mmsx_get_seekable (mmssrc->connection))
371     return FALSE;
372 
373   *size = mmsx_get_length (mmssrc->connection);
374   return TRUE;
375 }
376 
377 static gboolean
gst_mms_start(GstBaseSrc * bsrc)378 gst_mms_start (GstBaseSrc * bsrc)
379 {
380   GstMMS *mms = GST_MMS (bsrc);
381   guint bandwidth_avail;
382 
383   if (!mms->uri_name || *mms->uri_name == '\0')
384     goto no_uri;
385 
386   if (mms->connection_speed)
387     bandwidth_avail = mms->connection_speed;
388   else
389     bandwidth_avail = G_MAXINT;
390 
391   /* If we already have a connection, and the uri isn't changed, reuse it,
392      as connecting is expensive. */
393   if (mms->connection) {
394     if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
395       GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
396           mms->uri_name);
397       return TRUE;
398     } else {
399       mmsx_close (mms->connection);
400       g_free (mms->current_connection_uri_name);
401       mms->current_connection_uri_name = NULL;
402     }
403   }
404 
405   /* FIXME: pass some sane arguments here */
406   GST_DEBUG_OBJECT (mms,
407       "Trying mms_connect (%s) with bandwidth constraint of %d bps",
408       mms->uri_name, bandwidth_avail);
409   mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
410   if (mms->connection) {
411     /* Save the uri name so that it can be checked for connection reusing,
412        see above. */
413     mms->current_connection_uri_name = g_strdup (mms->uri_name);
414     GST_DEBUG_OBJECT (mms, "Connect successful");
415     return TRUE;
416   } else {
417     gchar *url, *location;
418 
419     GST_ERROR_OBJECT (mms,
420         "Could not connect to this stream, redirecting to rtsp");
421     location = strstr (mms->uri_name, "://");
422     if (location == NULL || *location == '\0' || *(location + 3) == '\0')
423       goto no_uri;
424     url = g_strdup_printf ("rtsp://%s", location + 3);
425 
426     gst_element_post_message (GST_ELEMENT_CAST (mms),
427         gst_message_new_element (GST_OBJECT_CAST (mms),
428             gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
429                 NULL)));
430 
431     /* post an error message as well, so that applications that don't handle
432      * redirect messages get to see a proper error message */
433     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
434         ("Could not connect to streaming server."),
435         ("A redirect message was posted on the bus and should have been "
436             "handled by the application."));
437 
438     return FALSE;
439   }
440 
441 no_uri:
442   {
443     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
444         ("No URI to open specified"), (NULL));
445     return FALSE;
446   }
447 }
448 
449 static gboolean
gst_mms_stop(GstBaseSrc * bsrc)450 gst_mms_stop (GstBaseSrc * bsrc)
451 {
452   GstMMS *mms = GST_MMS (bsrc);
453 
454   if (mms->connection != NULL) {
455     /* Check if the connection is still pristine, that is if no more then
456        just the mmslib cached asf header has been read. If it is still pristine
457        preserve it as we often are re-started with the same URL and connecting
458        is expensive */
459     if (mmsx_get_current_pos (mms->connection) >
460         mmsx_get_asf_header_len (mms->connection)) {
461       mmsx_close (mms->connection);
462       mms->connection = NULL;
463       g_free (mms->current_connection_uri_name);
464       mms->current_connection_uri_name = NULL;
465     }
466   }
467   return TRUE;
468 }
469 
470 static void
gst_mms_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)471 gst_mms_set_property (GObject * object, guint prop_id,
472     const GValue * value, GParamSpec * pspec)
473 {
474   GstMMS *mmssrc = GST_MMS (object);
475 
476   switch (prop_id) {
477     case PROP_LOCATION:
478       gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
479           g_value_get_string (value), NULL);
480       break;
481     case PROP_CONNECTION_SPEED:
482       GST_OBJECT_LOCK (mmssrc);
483       mmssrc->connection_speed = g_value_get_uint64 (value) * 1000;
484       GST_OBJECT_UNLOCK (mmssrc);
485       break;
486     default:
487       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
488       break;
489   }
490 }
491 
492 static void
gst_mms_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)493 gst_mms_get_property (GObject * object, guint prop_id,
494     GValue * value, GParamSpec * pspec)
495 {
496   GstMMS *mmssrc = GST_MMS (object);
497 
498   GST_OBJECT_LOCK (mmssrc);
499   switch (prop_id) {
500     case PROP_LOCATION:
501       if (mmssrc->uri_name)
502         g_value_set_string (value, mmssrc->uri_name);
503       break;
504     case PROP_CONNECTION_SPEED:
505       g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
506       break;
507     default:
508       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
509       break;
510   }
511   GST_OBJECT_UNLOCK (mmssrc);
512 }
513 
514 /* entry point to initialize the plug-in
515  * initialize the plug-in itself
516  * register the element factories and pad templates
517  * register the features
518  */
519 static gboolean
plugin_init(GstPlugin * plugin)520 plugin_init (GstPlugin * plugin)
521 {
522   return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
523 }
524 
525 static GstURIType
gst_mms_uri_get_type(GType type)526 gst_mms_uri_get_type (GType type)
527 {
528   return GST_URI_SRC;
529 }
530 
531 static const gchar *const *
gst_mms_uri_get_protocols(GType type)532 gst_mms_uri_get_protocols (GType type)
533 {
534   static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
535 
536   return protocols;
537 }
538 
539 static gchar *
gst_mms_uri_get_uri(GstURIHandler * handler)540 gst_mms_uri_get_uri (GstURIHandler * handler)
541 {
542   GstMMS *src = GST_MMS (handler);
543 
544   /* FIXME: make thread-safe */
545   return g_strdup (src->uri_name);
546 }
547 
548 static gchar *
gst_mms_src_make_valid_uri(const gchar * uri)549 gst_mms_src_make_valid_uri (const gchar * uri)
550 {
551   gchar *protocol;
552   const gchar *colon, *tmp;
553   gsize len;
554 
555   if (!uri || !gst_uri_is_valid (uri))
556     return NULL;
557 
558   protocol = gst_uri_get_protocol (uri);
559 
560   if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
561       (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
562     g_free (protocol);
563     return FALSE;
564   }
565   g_free (protocol);
566 
567   colon = strstr (uri, "://");
568   if (!colon)
569     return NULL;
570 
571   tmp = colon + 3;
572   len = strlen (tmp);
573   if (len == 0)
574     return NULL;
575 
576   /* libmms segfaults if there's no hostname or
577    * no / after the hostname
578    */
579   colon = strstr (tmp, "/");
580   if (colon == tmp)
581     return NULL;
582 
583   if (strstr (tmp, "/") == NULL) {
584     gchar *ret;
585 
586     len = strlen (uri);
587     ret = g_malloc0 (len + 2);
588     memcpy (ret, uri, len);
589     ret[len] = '/';
590     return ret;
591   } else {
592     return g_strdup (uri);
593   }
594 }
595 
596 static gboolean
gst_mms_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)597 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
598     GError ** error)
599 {
600   GstMMS *src = GST_MMS (handler);
601   gchar *fixed_uri;
602 
603   fixed_uri = gst_mms_src_make_valid_uri (uri);
604   if (!fixed_uri && uri) {
605     g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
606         "Invalid MMS URI");
607     return FALSE;
608   }
609 
610   GST_OBJECT_LOCK (src);
611   g_free (src->uri_name);
612   src->uri_name = fixed_uri;
613   GST_OBJECT_UNLOCK (src);
614 
615   return TRUE;
616 }
617 
618 static void
gst_mms_uri_handler_init(gpointer g_iface,gpointer iface_data)619 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
620 {
621   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
622 
623   iface->get_type = gst_mms_uri_get_type;
624   iface->get_protocols = gst_mms_uri_get_protocols;
625   iface->get_uri = gst_mms_uri_get_uri;
626   iface->set_uri = gst_mms_uri_set_uri;
627 }
628 
629 
630 /* this is the structure that gst-register looks for
631  * so keep the name plugin_desc, or you cannot get your plug-in registered */
632 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
633     GST_VERSION_MINOR,
634     mms,
635     "Microsoft Multi Media Server streaming protocol support",
636     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
637