• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2020 Julien Isorce <jisorce@oblong.com>
3  *
4  * gstgssrc.c:
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 /**
23  * SECTION:element-gssrc
24  * @title: gssrc
25  * @see_also: #GstGsSrc
26  *
27  * Read data from a file in a Google Cloud Storage.
28  *
29  * ## Example launch line
30  * ```
31  * gst-launch-1.0 gssrc location=gs://mybucket/myvideo.mkv ! decodebin !
32  * glimagesink
33  * ```
34  * ### Play a video from a Google Cloud Storage.
35  * ```
36  * gst-launch-1.0 gssrc location=gs://mybucket/myvideo.mkv ! decodebin ! navseek
37  * seek-offset=10 ! glimagesink
38  * ```
39  * ### Play a video from a Google Cloud Storage and seek using the keyboard
40  * from the terminal.
41  *
42  * See also: #GstGsSink
43  * Since: 1.20
44  */
45 
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
49 
50 #include "gstgscommon.h"
51 #include "gstgssrc.h"
52 
53 static GstStaticPadTemplate srctemplate =
54     GST_STATIC_PAD_TEMPLATE("src",
55                             GST_PAD_SRC,
56                             GST_PAD_ALWAYS,
57                             GST_STATIC_CAPS_ANY);
58 
59 GST_DEBUG_CATEGORY_STATIC(gst_gs_src_debug);
60 #define GST_CAT_DEFAULT gst_gs_src_debug
61 
62 enum { LAST_SIGNAL };
63 
64 // https://github.com/googleapis/google-cloud-cpp/issues/2657
65 #define DEFAULT_BLOCKSIZE 3 * 1024 * 1024 / 2
66 
67 enum {
68   PROP_0,
69   PROP_LOCATION,
70   PROP_SERVICE_ACCOUNT_EMAIL,
71   PROP_SERVICE_ACCOUNT_CREDENTIALS
72 };
73 
74 class GSReadStream;
75 
76 struct _GstGsSrc {
77   GstBaseSrc parent;
78 
79   std::unique_ptr<google::cloud::storage::Client> gcs_client;
80   std::unique_ptr<GSReadStream> gcs_stream;
81   gchar* uri;
82   gchar* service_account_email;
83   gchar* service_account_credentials;
84   std::string bucket_name;
85   std::string object_name;
86   guint64 read_position;
87   guint64 object_size;
88 };
89 
90 static void gst_gs_src_finalize(GObject* object);
91 
92 static void gst_gs_src_set_property(GObject* object,
93                                     guint prop_id,
94                                     const GValue* value,
95                                     GParamSpec* pspec);
96 static void gst_gs_src_get_property(GObject* object,
97                                     guint prop_id,
98                                     GValue* value,
99                                     GParamSpec* pspec);
100 
101 static gboolean gst_gs_src_start(GstBaseSrc* basesrc);
102 static gboolean gst_gs_src_stop(GstBaseSrc* basesrc);
103 
104 static gboolean gst_gs_src_is_seekable(GstBaseSrc* src);
105 static gboolean gst_gs_src_get_size(GstBaseSrc* src, guint64* size);
106 static GstFlowReturn gst_gs_src_fill(GstBaseSrc* src,
107                                      guint64 offset,
108                                      guint length,
109                                      GstBuffer* buf);
110 static gboolean gst_gs_src_query(GstBaseSrc* src, GstQuery* query);
111 
112 static void gst_gs_src_uri_handler_init(gpointer g_iface, gpointer iface_data);
113 
114 #define _do_init                                                            \
115   G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, gst_gs_src_uri_handler_init); \
116   GST_DEBUG_CATEGORY_INIT(gst_gs_src_debug, "gssrc", 0, "gssrc element");
117 #define gst_gs_src_parent_class parent_class
118 G_DEFINE_TYPE_WITH_CODE(GstGsSrc, gst_gs_src, GST_TYPE_BASE_SRC, _do_init);
119 GST_ELEMENT_REGISTER_DEFINE(gssrc, "gssrc", GST_RANK_NONE, GST_TYPE_GS_SRC)
120 
121 namespace gcs = google::cloud::storage;
122 
123 class GSReadStream {
124  public:
GSReadStream(GstGsSrc * src,const std::int64_t start=0,const std::int64_t end=-1)125   GSReadStream(GstGsSrc* src,
126                const std::int64_t start = 0,
127                const std::int64_t end = -1)
128       : gcs_stream_(src->gcs_client->ReadObject(src->bucket_name,
129                                                 src->object_name,
130                                                 gcs::ReadFromOffset(start))) {}
~GSReadStream()131   ~GSReadStream() { gcs_stream_.Close(); }
132 
stream()133   gcs::ObjectReadStream& stream() { return gcs_stream_; }
134 
135  private:
136   gcs::ObjectReadStream gcs_stream_;
137 };
138 
gst_gs_src_class_init(GstGsSrcClass * klass)139 static void gst_gs_src_class_init(GstGsSrcClass* klass) {
140   GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
141   GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
142   GstBaseSrcClass* gstbasesrc_class = GST_BASE_SRC_CLASS(klass);
143 
144   gobject_class->set_property = gst_gs_src_set_property;
145   gobject_class->get_property = gst_gs_src_get_property;
146 
147   /**
148    * GstGsSink:location:
149    *
150    * Name of the Google Cloud Storage bucket.
151    *
152    * Since: 1.20
153    */
154   g_object_class_install_property(
155       gobject_class, PROP_LOCATION,
156       g_param_spec_string(
157           "location", "File Location", "Location of the file to read", NULL,
158           (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
159                         GST_PARAM_MUTABLE_READY)));
160 
161   /**
162    * GstGsSrc:service-account-email:
163    *
164    * Service Account Email to use for credentials.
165    *
166    * Since: 1.20
167    */
168   g_object_class_install_property(
169       gobject_class, PROP_SERVICE_ACCOUNT_EMAIL,
170       g_param_spec_string(
171           "service-account-email", "Service Account Email",
172           "Service Account Email to use for credentials", NULL,
173           (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
174                         GST_PARAM_MUTABLE_READY)));
175 
176   /**
177    * GstGsSrc:service-account-credentials:
178    *
179    * Service Account Credentials as a JSON string to use for credentials.
180    *
181    * Since: 1.20
182    */
183   g_object_class_install_property(
184       gobject_class, PROP_SERVICE_ACCOUNT_CREDENTIALS,
185       g_param_spec_string(
186           "service-account-credentials", "Service Account Credentials",
187           "Service Account Credentials as a JSON string to use for credentials",
188           NULL,
189           (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
190                         GST_PARAM_MUTABLE_READY)));
191 
192   gobject_class->finalize = gst_gs_src_finalize;
193 
194   gst_element_class_set_static_metadata(
195       gstelement_class, "Google Cloud Storage Source", "Source/File",
196       "Read from arbitrary point from a file in a Google Cloud Storage",
197       "Julien Isorce <jisorce@oblong.com>");
198   gst_element_class_add_static_pad_template(gstelement_class, &srctemplate);
199 
200   gstbasesrc_class->start = GST_DEBUG_FUNCPTR(gst_gs_src_start);
201   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR(gst_gs_src_stop);
202   gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR(gst_gs_src_is_seekable);
203   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR(gst_gs_src_get_size);
204   gstbasesrc_class->fill = GST_DEBUG_FUNCPTR(gst_gs_src_fill);
205   gstbasesrc_class->query = GST_DEBUG_FUNCPTR(gst_gs_src_query);
206 }
207 
gst_gs_src_init(GstGsSrc * src)208 static void gst_gs_src_init(GstGsSrc* src) {
209   src->gcs_stream = nullptr;
210   src->uri = NULL;
211   src->service_account_email = NULL;
212   src->service_account_credentials = NULL;
213   src->read_position = 0;
214   src->object_size = 0;
215 
216   gst_base_src_set_blocksize(GST_BASE_SRC(src), DEFAULT_BLOCKSIZE);
217   gst_base_src_set_dynamic_size(GST_BASE_SRC(src), FALSE);
218   gst_base_src_set_live(GST_BASE_SRC(src), FALSE);
219 }
220 
gst_gs_src_finalize(GObject * object)221 static void gst_gs_src_finalize(GObject* object) {
222   GstGsSrc* src = GST_GS_SRC(object);
223 
224   g_free(src->uri);
225   src->uri = NULL;
226   g_free(src->service_account_email);
227   src->service_account_email = NULL;
228   g_free(src->service_account_credentials);
229   src->service_account_credentials = NULL;
230   src->read_position = 0;
231   src->object_size = 0;
232 
233   G_OBJECT_CLASS(parent_class)->finalize(object);
234 }
235 
gst_gs_src_set_location(GstGsSrc * src,const gchar * location,GError ** err)236 static gboolean gst_gs_src_set_location(GstGsSrc* src,
237                                         const gchar* location,
238                                         GError** err) {
239   GstState state = GST_STATE_NULL;
240   std::string filepath = location;
241   size_t delimiter = std::string::npos;
242 
243   // The element must be stopped in order to do this.
244   GST_OBJECT_LOCK(src);
245   state = GST_STATE(src);
246   if (state != GST_STATE_READY && state != GST_STATE_NULL)
247     goto wrong_state;
248   GST_OBJECT_UNLOCK(src);
249 
250   g_free(src->uri);
251   src->uri = NULL;
252 
253   if (location) {
254     if (g_str_has_prefix(location, "gs://")) {
255       src->uri = g_strdup(location);
256       filepath = filepath.substr(5);
257     } else {
258       src->uri = g_strdup_printf("gs://%s", location);
259       filepath = location;
260     }
261 
262     delimiter = filepath.find_first_of('/');
263     if (delimiter == std::string::npos)
264       goto wrong_location;
265 
266     std::string bucket_name = filepath.substr(0, delimiter);
267     src->bucket_name = bucket_name;
268     src->object_name = filepath.substr(delimiter + 1);
269 
270     GST_INFO_OBJECT(src, "uri is %s", src->uri);
271     GST_INFO_OBJECT(src, "bucket name is %s", src->bucket_name.c_str());
272     GST_INFO_OBJECT(src, "object name is %s", src->object_name.c_str());
273   }
274   g_object_notify(G_OBJECT(src), "location");
275 
276   return TRUE;
277 
278   // ERROR.
279 wrong_state : {
280   g_warning(
281       "Changing the `location' property on gssrc when a file is open"
282       "is not supported.");
283   if (err)
284     g_set_error(
285         err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
286         "Changing the `location' property on gssrc when a file is open is "
287         "not supported.");
288   GST_OBJECT_UNLOCK(src);
289   return FALSE;
290 }
291 wrong_location : {
292   if (err)
293     g_set_error(err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
294                 "Failed to find a bucket name");
295   GST_OBJECT_UNLOCK(src);
296   return FALSE;
297 }
298 }
299 
gst_gs_src_set_service_account_email(GstGsSrc * src,const gchar * service_account_email)300 static gboolean gst_gs_src_set_service_account_email(
301     GstGsSrc* src,
302     const gchar* service_account_email) {
303   if (GST_STATE(src) == GST_STATE_PLAYING ||
304       GST_STATE(src) == GST_STATE_PAUSED) {
305     GST_WARNING_OBJECT(src,
306                        "Setting a new service account email not supported in "
307                        "PLAYING or PAUSED state");
308     return FALSE;
309   }
310 
311   GST_OBJECT_LOCK(src);
312   g_free(src->service_account_email);
313   src->service_account_email = NULL;
314 
315   if (service_account_email)
316     src->service_account_email = g_strdup(service_account_email);
317 
318   GST_OBJECT_UNLOCK(src);
319 
320   return TRUE;
321 }
322 
gst_gs_src_set_service_account_credentials(GstGsSrc * src,const gchar * service_account_credentials)323 static gboolean gst_gs_src_set_service_account_credentials(
324     GstGsSrc* src,
325     const gchar* service_account_credentials) {
326   if (GST_STATE(src) == GST_STATE_PLAYING ||
327       GST_STATE(src) == GST_STATE_PAUSED) {
328     GST_WARNING_OBJECT(
329         src,
330         "Setting a new service account credentials not supported in "
331         "PLAYING or PAUSED state");
332     return FALSE;
333   }
334 
335   GST_OBJECT_LOCK(src);
336   g_free(src->service_account_credentials);
337   src->service_account_credentials = NULL;
338 
339   if (service_account_credentials)
340     src->service_account_credentials = g_strdup(service_account_credentials);
341 
342   GST_OBJECT_UNLOCK(src);
343 
344   return TRUE;
345 }
346 
gst_gs_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)347 static void gst_gs_src_set_property(GObject* object,
348                                     guint prop_id,
349                                     const GValue* value,
350                                     GParamSpec* pspec) {
351   GstGsSrc* src = GST_GS_SRC(object);
352 
353   g_return_if_fail(GST_IS_GS_SRC(object));
354 
355   switch (prop_id) {
356     case PROP_LOCATION:
357       gst_gs_src_set_location(src, g_value_get_string(value), NULL);
358       break;
359     case PROP_SERVICE_ACCOUNT_EMAIL:
360       gst_gs_src_set_service_account_email(src, g_value_get_string(value));
361       break;
362     case PROP_SERVICE_ACCOUNT_CREDENTIALS:
363       gst_gs_src_set_service_account_credentials(src,
364                                                  g_value_get_string(value));
365       break;
366     default:
367       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
368       break;
369   }
370 }
371 
gst_gs_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)372 static void gst_gs_src_get_property(GObject* object,
373                                     guint prop_id,
374                                     GValue* value,
375                                     GParamSpec* pspec) {
376   GstGsSrc* src = GST_GS_SRC(object);
377 
378   g_return_if_fail(GST_IS_GS_SRC(object));
379 
380   switch (prop_id) {
381     case PROP_LOCATION:
382       GST_OBJECT_LOCK(src);
383       g_value_set_string(value, src->uri);
384       GST_OBJECT_UNLOCK(src);
385       break;
386     case PROP_SERVICE_ACCOUNT_EMAIL:
387       GST_OBJECT_LOCK(src);
388       g_value_set_string(value, src->service_account_email);
389       GST_OBJECT_UNLOCK(src);
390       break;
391     case PROP_SERVICE_ACCOUNT_CREDENTIALS:
392       GST_OBJECT_LOCK(src);
393       g_value_set_string(value, src->service_account_credentials);
394       GST_OBJECT_UNLOCK(src);
395       break;
396     default:
397       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
398       break;
399   }
400 }
401 
gst_gs_read_stream(GstGsSrc * src,guint8 * data,const guint64 offset,const guint length)402 static gint gst_gs_read_stream(GstGsSrc* src,
403                                guint8* data,
404                                const guint64 offset,
405                                const guint length) {
406   gint gcount = 0;
407   gchar* sdata = reinterpret_cast<gchar*>(data);
408 
409   gcs::ObjectReadStream& stream = src->gcs_stream->stream();
410 
411   while (!stream.eof()) {
412     stream.read(sdata, length);
413     if (stream.status().ok())
414       break;
415 
416     GST_ERROR_OBJECT(src, "Restart after (%s)",
417                      stream.status().message().c_str());
418     src->gcs_stream = std::make_unique<GSReadStream>(src, offset);
419   }
420 
421   gcount = stream.gcount();
422 
423   GST_INFO_OBJECT(src, "Client read %d bytes", gcount);
424 
425   return gcount;
426 }
427 
gst_gs_src_fill(GstBaseSrc * basesrc,guint64 offset,guint length,GstBuffer * buf)428 static GstFlowReturn gst_gs_src_fill(GstBaseSrc* basesrc,
429                                      guint64 offset,
430                                      guint length,
431                                      GstBuffer* buf) {
432   GstGsSrc* src = GST_GS_SRC(basesrc);
433   guint to_read = 0;
434   guint bytes_read = 0;
435   gint ret = 0;
436   GstMapInfo info = {};
437   guint8* data = NULL;
438 
439   if (G_UNLIKELY(offset != (guint64)-1 && src->read_position != offset)) {
440     src->gcs_stream = std::make_unique<GSReadStream>(src, offset);
441     src->read_position = offset;
442   }
443 
444   if (!gst_buffer_map(buf, &info, GST_MAP_WRITE))
445     goto buffer_write_fail;
446 
447   data = info.data;
448 
449   bytes_read = 0;
450   to_read = length;
451   while (to_read > 0) {
452     GST_INFO_OBJECT(src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x",
453                     to_read, offset + bytes_read);
454 
455     ret = gst_gs_read_stream(src, data + bytes_read, offset, to_read);
456     if (G_UNLIKELY(ret < 0))
457       goto could_not_read;
458 
459     if (G_UNLIKELY(ret == 0)) {
460       // Push any remaining data.
461       if (bytes_read > 0)
462         break;
463       goto eos;
464     }
465 
466     to_read -= ret;
467     bytes_read += ret;
468 
469     src->read_position += ret;
470   }
471 
472   GST_INFO_OBJECT(
473       src, "Read %" G_GUINT32_FORMAT " bytes of %" G_GUINT32_FORMAT " length",
474       bytes_read, length);
475 
476   gst_buffer_unmap(buf, &info);
477   if (bytes_read != length)
478     gst_buffer_resize(buf, 0, bytes_read);
479 
480   GST_BUFFER_OFFSET(buf) = offset;
481   GST_BUFFER_OFFSET_END(buf) = offset + bytes_read;
482 
483   return GST_FLOW_OK;
484 
485   // ERROR.
486 could_not_read : {
487   GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM);
488   gst_buffer_unmap(buf, &info);
489   gst_buffer_resize(buf, 0, 0);
490   return GST_FLOW_ERROR;
491 }
492 eos : {
493   GST_INFO_OBJECT(src, "EOS");
494   gst_buffer_unmap(buf, &info);
495   gst_buffer_resize(buf, 0, 0);
496   return GST_FLOW_EOS;
497 }
498 buffer_write_fail : {
499   GST_ELEMENT_ERROR(src, RESOURCE, WRITE, (NULL), ("Can't write to buffer"));
500   return GST_FLOW_ERROR;
501 }
502 }
503 
gst_gs_src_is_seekable(GstBaseSrc * basesrc)504 static gboolean gst_gs_src_is_seekable(GstBaseSrc* basesrc) {
505   return TRUE;
506 }
507 
gst_gs_src_get_size(GstBaseSrc * basesrc,guint64 * size)508 static gboolean gst_gs_src_get_size(GstBaseSrc* basesrc, guint64* size) {
509   GstGsSrc* src = GST_GS_SRC(basesrc);
510 
511   *size = src->object_size;
512 
513   return TRUE;
514 }
515 
gst_gs_src_start(GstBaseSrc * basesrc)516 static gboolean gst_gs_src_start(GstBaseSrc* basesrc) {
517   GstGsSrc* src = GST_GS_SRC(basesrc);
518   GError* err = NULL;
519 
520   src->read_position = 0;
521   src->object_size = 0;
522 
523   if (src->uri == NULL || src->uri[0] == '\0') {
524     GST_ELEMENT_ERROR(src, RESOURCE, NOT_FOUND,
525                       ("No uri specified for reading."), (NULL));
526     return FALSE;
527   }
528 
529   GST_INFO_OBJECT(src, "Opening file %s", src->uri);
530 
531   src->gcs_client = gst_gs_create_client(
532       src->service_account_email, src->service_account_credentials, &err);
533   if (err) {
534     GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ,
535                       ("Could not create client (%s)", err->message),
536                       GST_ERROR_SYSTEM);
537     g_clear_error(&err);
538     return FALSE;
539   }
540 
541   GST_INFO_OBJECT(src, "Parsed bucket name (%s) and object name (%s)",
542                   src->bucket_name.c_str(), src->object_name.c_str());
543 
544   google::cloud::StatusOr<gcs::ObjectMetadata> object_metadata =
545       src->gcs_client->GetObjectMetadata(src->bucket_name, src->object_name);
546   if (!object_metadata) {
547     GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ,
548                       ("Could not get object metadata (%s)",
549                        object_metadata.status().message().c_str()),
550                       GST_ERROR_SYSTEM);
551     return FALSE;
552   }
553 
554   src->object_size = object_metadata->size();
555 
556   GST_INFO_OBJECT(src, "Object size %" G_GUINT64_FORMAT "\n", src->object_size);
557 
558   src->gcs_stream = std::make_unique<GSReadStream>(src);
559 
560   return TRUE;
561 }
562 
gst_gs_src_stop(GstBaseSrc * basesrc)563 static gboolean gst_gs_src_stop(GstBaseSrc* basesrc) {
564   GstGsSrc* src = GST_GS_SRC(basesrc);
565 
566   src->gcs_stream = nullptr;
567   src->read_position = 0;
568   src->object_size = 0;
569 
570   return TRUE;
571 }
572 
gst_gs_src_query(GstBaseSrc * src,GstQuery * query)573 static gboolean gst_gs_src_query(GstBaseSrc* src, GstQuery* query) {
574   gboolean ret;
575 
576   switch (GST_QUERY_TYPE(query)) {
577     case GST_QUERY_SCHEDULING: {
578       // A pushsrc can by default never operate in pull mode override
579       // if you want something different.
580       gst_query_set_scheduling(query, GST_SCHEDULING_FLAG_SEQUENTIAL, 1, -1, 0);
581       gst_query_add_scheduling_mode(query, GST_PAD_MODE_PUSH);
582 
583       ret = TRUE;
584       break;
585     }
586     default:
587       ret = GST_BASE_SRC_CLASS(parent_class)->query(src, query);
588       break;
589   }
590   return ret;
591 }
592 
gst_gs_src_uri_get_type(GType type)593 static GstURIType gst_gs_src_uri_get_type(GType type) {
594   return GST_URI_SRC;
595 }
596 
gst_gs_src_uri_get_protocols(GType type)597 static const gchar* const* gst_gs_src_uri_get_protocols(GType type) {
598   static const gchar* protocols[] = {"gs", NULL};
599 
600   return protocols;
601 }
602 
gst_gs_src_uri_get_uri(GstURIHandler * handler)603 static gchar* gst_gs_src_uri_get_uri(GstURIHandler* handler) {
604   GstGsSrc* src = GST_GS_SRC(handler);
605 
606   return g_strdup(src->uri);
607 }
608 
gst_gs_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** err)609 static gboolean gst_gs_src_uri_set_uri(GstURIHandler* handler,
610                                        const gchar* uri,
611                                        GError** err) {
612   GstGsSrc* src = GST_GS_SRC(handler);
613 
614   if (strcmp(uri, "gs://") == 0) {
615     // Special case for "gs://" as this is used by some applications
616     // to test with gst_element_make_from_uri if there's an element
617     // that supports the URI protocol.
618     gst_gs_src_set_location(src, NULL, NULL);
619     return TRUE;
620   }
621 
622   return gst_gs_src_set_location(src, uri, err);
623 }
624 
gst_gs_src_uri_handler_init(gpointer g_iface,gpointer iface_data)625 static void gst_gs_src_uri_handler_init(gpointer g_iface, gpointer iface_data) {
626   GstURIHandlerInterface* iface = (GstURIHandlerInterface*)g_iface;
627 
628   iface->get_type = gst_gs_src_uri_get_type;
629   iface->get_protocols = gst_gs_src_uri_get_protocols;
630   iface->get_uri = gst_gs_src_uri_get_uri;
631   iface->set_uri = gst_gs_src_uri_set_uri;
632 }
633