No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2006 James Livingston <doclivingston@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 *
27 */
28
29 #include <config.h>
30
31 #include "rb-player.h"
32 #include "rb-player-gst.h"
33 #include "rb-player-gst-xfade.h"
34 #include "rb-marshal.h"
35
36 /**
37 * RBPlayerPlayType:
38 * @RB_PLAYER_PLAY_REPLACE: Replace the existing stream
39 * @RB_PLAYER_PLAY_AFTER_EOS: Start the new stream after the current stream ends
40 * @RB_PLAYER_PLAY_CROSSFADE: Crossfade between the existing stream and the new stream
41 */
42
43 /**
44 * RBPlayerError:
45 * @RB_PLAYER_ERROR_NO_AUDIO: Audio playback not available
46 * @RB_PLAYER_ERROR_GENERAL: Nonspecific error
47 * @RB_PLAYER_ERROR_INTERNAL: Internal error
48 * @RB_PLAYER_ERROR_NOT_FOUND: The resource could not be found
49 */
50
51 /* Signals */
52 enum {
53 EOS,
54 INFO,
55 BUFFERING,
56 ERROR,
57 TICK,
58 EVENT,
59 PLAYING_STREAM,
60 VOLUME_CHANGED,
61 IMAGE,
62 REDIRECT,
63 LAST_SIGNAL
64 };
65
66 static guint signals[LAST_SIGNAL] = { 0 };
67
68 /**
69 * SECTION:rb-player
70 * @short_description: playback backend interface
71 * @include: rb-player.h
72 *
73 * This is the interface implemented by the rhythmbox playback backends.
74 * It allows the caller to control playback (open, play, pause, close),
75 * seek (set_time), control volume (get_volume, set_volume)
76 * and receive playback state information (get_time, various signals).
77 *
78 * The playback interface allows for multiple streams to be playing (or at
79 * least open) concurrently. The caller associates some data with each stream
80 * it opens (#rb_player_open), which is included in the paramters with each
81 * signal emitted. The caller should not assume that the new stream is playing
82 * immediately upon returning from #rb_player_play. Instead, it should use
83 * the 'playing-stream' signal to determine that.
84 *
85 * The player implementation should emit signals for metadata extracted from the
86 * stream using the 'info' signal
87 *
88 * While playing, the player implementation should emit 'tick' signals frequently
89 * enough to update an elapsed/remaining time display consistently. The duration
90 * value included in tick signal emissions is used to prepare the next stream before
91 * the current stream reaches EOS, so it should be updated for each emission to account
92 * for variable bitrate streams that produce inaccurate duration estimates early on.
93 *
94 * When playing a stream from the network, the player can report buffering status
95 * using the 'buffering' signal. The value included in the signal indicates the
96 * percentage of the buffer that has been filled.
97 *
98 * The 'event' signal can be used to communicate events from the player to the application.
99 * For GStreamer-based player implementations, events are triggered by elements in the
100 * pipeline posting application messages. The name of the message becomes the name of the
101 * event.
102 */
103
104 static void
105 rb_player_interface_init (RBPlayerIface *iface)
106 {
107 /**
108 * RBPlayer::eos:
109 * @player: the #RBPlayer
110 * @stream_data: the data associated with the stream that finished
111 * @early: if %TRUE, the EOS notification should only be used for track changes.
112 *
113 * The 'eos' signal is emitted when a stream finishes, or in some cases, when it
114 * is about to finish (with @early set to %TRUE) to allow for a new track to be
115 * played immediately afterwards.
116 **/
117 signals[EOS] =
118 g_signal_new ("eos",
119 G_TYPE_FROM_INTERFACE (iface),
120 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
121 G_STRUCT_OFFSET (RBPlayerIface, eos),
122 NULL, NULL,
123 rb_marshal_VOID__POINTER_BOOLEAN,
124 G_TYPE_NONE,
125 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
126
127 /**
128 * RBPlayer::info:
129 * @player: the #RBPlayer
130 * @stream_data: the data associated with the stream
131 * @field: the #RBMetaDataField corresponding to the stream info
132 * @value: the value of the stream info field
133 *
134 * The 'info' signal is emitted when a metadata value is found in
135 * the stream.
136 **/
137 signals[INFO] =
138 g_signal_new ("info",
139 G_TYPE_FROM_INTERFACE (iface),
140 G_SIGNAL_RUN_LAST,
141 G_STRUCT_OFFSET (RBPlayerIface, info),
142 NULL, NULL,
143 rb_marshal_VOID__POINTER_INT_POINTER,
144 G_TYPE_NONE,
145 3, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_VALUE);
146
147 /**
148 * RBPlayer::error:
149 * @player: the #RBPlayer
150 * @stream_data: the data associated with the stream
151 * @error: description of the error
152 *
153 * The 'error' signal is emitted when an error is encountered
154 * while opening or playing a stream.
155 **/
156 signals[ERROR] =
157 g_signal_new ("error",
158 G_TYPE_FROM_INTERFACE (iface),
159 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
160 G_STRUCT_OFFSET (RBPlayerIface, error),
161 NULL, NULL,
162 rb_marshal_VOID__POINTER_POINTER,
163 G_TYPE_NONE,
164 2,
165 G_TYPE_POINTER, G_TYPE_POINTER);
166
167 /**
168 * RBPlayer::tick:
169 * @player: the #RBPlayer
170 * @stream_data: the data associated with the stream
171 * @elapsed: playback position in the stream (in nanoseconds)
172 * @duration: current estimate of the duration of the stream
173 * (in nanoseconds)
174 *
175 * The 'tick' signal is emitted repeatedly while the stream is
176 * playing. Signal handlers can use this to update UI and to
177 * prepare new streams for crossfade or gapless playback.
178 **/
179 signals[TICK] =
180 g_signal_new ("tick",
181 G_TYPE_FROM_INTERFACE (iface),
182 G_SIGNAL_RUN_LAST,
183 G_STRUCT_OFFSET (RBPlayerIface, tick),
184 NULL, NULL,
185 rb_marshal_VOID__POINTER_INT64_INT64,
186 G_TYPE_NONE,
187 3,
188 G_TYPE_POINTER, G_TYPE_INT64, G_TYPE_INT64);
189
190 /**
191 * RBPlayer::buffering:
192 * @player: the #RBPlayer
193 * @stream_data: the data associated with the buffering stream
194 * @progress: buffering percentage
195 *
196 * The 'buffering' signal is emitted while a stream is paused so
197 * that a buffer can be filled. The progress value typically varies
198 * from 0 to 100, and once it reaches 100, playback resumes.
199 **/
200 signals[BUFFERING] =
201 g_signal_new ("buffering",
202 G_TYPE_FROM_INTERFACE (iface),
203 G_SIGNAL_RUN_LAST,
204 G_STRUCT_OFFSET (RBPlayerIface, buffering),
205 NULL, NULL,
206 rb_marshal_VOID__POINTER_UINT,
207 G_TYPE_NONE,
208 2,
209 G_TYPE_POINTER, G_TYPE_UINT);
210
211 /**
212 * RBPlayer::event:
213 * @player: the #RBPlayer
214 * @stream_data: data associated with the stream
215 * @data: event data
216 *
217 * The 'event' signal provides a means for custom GStreamer
218 * elements to communicate events back to the rest of the
219 * application. The GStreamer element posts an application
220 * message on the GStreamer bus, which is translated into an
221 * event signal with the detail of the signal set to the name
222 * of the structure found in the message.
223 */
224 signals[EVENT] =
225 g_signal_new ("event",
226 G_TYPE_FROM_INTERFACE (iface),
227 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
228 G_STRUCT_OFFSET (RBPlayerIface, event),
229 NULL, NULL,
230 rb_marshal_VOID__POINTER_POINTER,
231 G_TYPE_NONE,
232 2,
233 G_TYPE_POINTER, G_TYPE_POINTER);
234
235 /**
236 * RBPlayer::playing-stream:
237 * @player: the #RBPlayer
238 * @stream_data: data associated with the stream
239 *
240 * The 'playing-stream' signal is emitted when the main playing stream
241 * changes. It should be used to update the UI to show the new
242 * stream. It can either be emitted before or after #rb_player_play returns,
243 * depending on the player backend.
244 */
245 signals[PLAYING_STREAM] =
246 g_signal_new ("playing-stream",
247 G_TYPE_FROM_INTERFACE (iface),
248 G_SIGNAL_RUN_LAST,
249 G_STRUCT_OFFSET (RBPlayerIface, playing_stream),
250 NULL, NULL,
251 g_cclosure_marshal_VOID__POINTER,
252 G_TYPE_NONE,
253 1,
254 G_TYPE_POINTER);
255 /**
256 * RBPlayer::volume-changed:
257 * @player: the #RBPlayer
258 * @volume: the new volume level
259 *
260 * The 'volume-changed' signal is emitted when the output stream volume is
261 * changed externally.
262 */
263 signals[VOLUME_CHANGED] =
264 g_signal_new ("volume-changed",
265 G_TYPE_FROM_INTERFACE (iface),
266 G_SIGNAL_RUN_LAST,
267 G_STRUCT_OFFSET (RBPlayerIface, volume_changed),
268 NULL, NULL,
269 g_cclosure_marshal_VOID__FLOAT,
270 G_TYPE_NONE,
271 1,
272 G_TYPE_FLOAT);
273
274 /**
275 * RBPlayer::image:
276 * @player: the #RBPlayer
277 * @stream_data: data associated with the stream
278 * @image: the image extracted from the stream
279 *
280 * The 'image' signal is emitted to provide access to images extracted
281 * from the stream.
282 */
283 signals[IMAGE] =
284 g_signal_new ("image",
285 G_TYPE_FROM_INTERFACE (iface),
286 G_SIGNAL_RUN_LAST,
287 G_STRUCT_OFFSET (RBPlayerIface, image),
288 NULL, NULL,
289 rb_marshal_VOID__POINTER_OBJECT,
290 G_TYPE_NONE,
291 2,
292 G_TYPE_POINTER, GDK_TYPE_PIXBUF);
293
294 /**
295 * RBPlayer::redirect:
296 * @player: the #RBPlayer
297 * @stream_data: data associated with the stream
298 * @uri: URI to redirect to
299 *
300 * The 'redirect' signal is emitted to indicate when a stream has change URI.
301 */
302 signals[REDIRECT] =
303 g_signal_new ("redirect",
304 G_TYPE_FROM_INTERFACE (iface),
305 G_SIGNAL_RUN_LAST,
306 G_STRUCT_OFFSET (RBPlayerIface, redirect),
307 NULL, NULL,
308 rb_marshal_VOID__POINTER_STRING,
309 G_TYPE_NONE,
310 2,
311 G_TYPE_POINTER, G_TYPE_STRING);
312 }
313
314 GType
315 rb_player_get_type (void)
316 {
317 static GType our_type = 0;
318
319 if (!our_type) {
320 static const GTypeInfo our_info = {
321 sizeof (RBPlayerIface),
322 NULL, /* base_init */
323 NULL, /* base_finalize */
324 (GClassInitFunc)rb_player_interface_init,
325 NULL, /* class_finalize */
326 NULL, /* class_data */
327 0,
328 0,
329 NULL
330 };
331
332 our_type = g_type_register_static (G_TYPE_INTERFACE, "RBPlayer", &our_info, 0);
333 }
334
335 return our_type;
336 }
337
338 /**
339 * rb_player_open:
340 * @player: a #RBPlayer
341 * @uri: URI to open
342 * @stream_data: arbitrary data to associate with the stream
343 * @stream_data_destroy: function to call to destroy the stream data
344 * @error: returns error information
345 *
346 * Prepares a stream for playback. Depending on the player
347 * implementation, this may stop any existing stream being
348 * played. The stream preparation process may continue
349 * asynchronously, in which case errors may be reported from
350 * #rb_player_play or using the 'error' signal.
351 *
352 * Return value: TRUE if the stream preparation was not unsuccessful
353 */
354 gboolean
355 rb_player_open (RBPlayer *player, const char *uri, gpointer stream_data, GDestroyNotify stream_data_destroy, GError **error)
356 {
357 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
358
359 return iface->open (player, uri, stream_data, stream_data_destroy, error);
360 }
361
362 /**
363 * rb_player_opened:
364 * @player: a #RBPlayer
365 *
366 * Determines whether a stream has been prepared for playback.
367 *
368 * Return value: TRUE if a stream is prepared for playback
369 */
370 gboolean
371 rb_player_opened (RBPlayer *player)
372 {
373 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
374
375 return iface->opened (player);
376 }
377
378 /**
379 * rb_player_close:
380 * @player: a #RBPlayer
381 * @uri: optionally, the URI of the stream to close
382 * @error: returns error information
383 *
384 * If a URI is specified, this will close the stream corresponding
385 * to that URI and free any resources related resources. If @uri
386 * is NULL, this will close all streams.
387 *
388 * If no streams remain open after this call, the audio device will
389 * be released.
390 *
391 * Return value: TRUE if a stream was found and closed
392 */
393 gboolean
394 rb_player_close (RBPlayer *player, const char *uri, GError **error)
395 {
396 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
397
398 return iface->close (player, uri, error);
399 }
400
401 /**
402 * rb_player_play:
403 * @player: a #RBPlayer
404 * @play_type: requested playback start type
405 * @crossfade: requested crossfade duration (nanoseconds)
406 * @error: returns error information
407 *
408 * Starts playback of the most recently opened stream.
409 * if @play_type is #RB_PLAYER_PLAY_CROSSFADE, the player
410 * may attempt to crossfade the new stream with any existing
411 * streams. If it does this, the it will use @crossfade as the
412 * duration of the fade.
413 *
414 * If @play_type is #RB_PLAYER_PLAY_AFTER_EOS, the player may
415 * attempt to start the stream immediately after the current
416 * playing stream reaches EOS. This may or may not result in
417 * the phenomemon known as 'gapless playback'.
418 *
419 * If @play_type is #RB_PLAYER_PLAY_REPLACE, the player will stop any
420 * existing stream before starting the new stream. It may do
421 * this anyway, regardless of the value of @play_type.
422 *
423 * The 'playing-stream' signal will be emitted when the new stream
424 * is actually playing. This may be before or after control returns
425 * to the caller.
426 *
427 * Return value: %TRUE if playback started successfully
428 */
429 gboolean
430 rb_player_play (RBPlayer *player, RBPlayerPlayType play_type, gint64 crossfade, GError **error)
431 {
432 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
433
434 return iface->play (player, play_type, crossfade, error);
435 }
436
437 /**
438 * rb_player_pause:
439 * @player: a #RBPlayer
440 *
441 * Pauses playback of the most recently started stream. Any
442 * streams being faded out may continue until the fade is
443 * complete.
444 */
445 void
446 rb_player_pause (RBPlayer *player)
447 {
448 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
449
450 iface->pause (player);
451 }
452
453 /**
454 * rb_player_playing:
455 * @player: a #RBPlayer.
456 *
457 * Determines whether the player is currently playing a stream.
458 * A stream is playing if it's not paused or being faded out.
459 *
460 * Return value: TRUE if playing
461 */
462 gboolean
463 rb_player_playing (RBPlayer *player)
464 {
465 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
466
467 return iface->playing (player);
468 }
469
470 /**
471 * rb_player_set_volume:
472 * @player: a #RBPlayer
473 * @volume: new output volume level
474 *
475 * Adjusts the output volume level. This affects all streams.
476 * The player may use a hardware volume control to implement
477 * this volume adjustment.
478 */
479 void
480 rb_player_set_volume (RBPlayer *player, float volume)
481 {
482 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
483
484 iface->set_volume (player, volume);
485 }
486
487 /**
488 * rb_player_get_volume:
489 * @player: a #RBPlayer
490 *
491 * Returns the current volume level, between 0.0 and 1.0.
492 *
493 * Return value: current output volume level
494 */
495 float
496 rb_player_get_volume (RBPlayer *player)
497 {
498 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
499
500 return iface->get_volume (player);
501 }
502
503 /**
504 * rb_player_seekable:
505 * @player: a #RBPlayer
506 *
507 * Determines whether seeking is supported for the current stream.
508 *
509 * Return value: TRUE if the current stream is seekable
510 */
511 gboolean
512 rb_player_seekable (RBPlayer *player)
513 {
514 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
515
516 return iface->seekable (player);
517 }
518
519 /**
520 * rb_player_set_time:
521 * @player: a #RBPlayer
522 * @newtime: seek target position in seconds
523 *
524 * Attempts to seek in the current stream. The player
525 * may ignore this if the stream is not seekable.
526 * The seek may take place asynchronously.
527 */
528 void
529 rb_player_set_time (RBPlayer *player, gint64 newtime)
530 {
531 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
532
533 iface->set_time (player, newtime);
534 }
535
536 /**
537 * rb_player_get_time:
538 * @player: a #RBPlayer
539 *
540 * Returns the current playback for the current stream in nanoseconds.
541 *
542 * Return value: playback position
543 */
544 gint64
545 rb_player_get_time (RBPlayer *player)
546 {
547 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
548
549 return iface->get_time (player);
550 }
551
552 /**
553 * rb_player_multiple_open:
554 * @player: a #RBPlayer
555 *
556 * Determines whether the player supports multiple open streams.
557 *
558 * Return value: TRUE if multiple open is supported
559 */
560 gboolean
561 rb_player_multiple_open (RBPlayer *player)
562 {
563 RBPlayerIface *iface = RB_PLAYER_GET_IFACE (player);
564
565 if (iface->multiple_open)
566 return iface->multiple_open (player);
567 else
568 return FALSE;
569 }
570
571 /**
572 * rb_player_new:
573 * @want_crossfade: if TRUE, try to use a backend that supports
574 * crossfading and other track transitions.
575 * @error: returns error information
576 *
577 * Creates a new player object.
578 *
579 * Return value: (transfer full): new player object.
580 */
581 RBPlayer*
582 rb_player_new (gboolean want_crossfade, GError **error)
583 {
584 if (want_crossfade)
585 return rb_player_gst_xfade_new (error);
586 else
587 return rb_player_gst_new (error);
588 }
589
590 /**
591 * _rb_player_emit_eos:
592 * @player: a #RBPlayer implementation
593 * @stream_data: data associated with the stream
594 * @early: whether this is an early EOS notification
595 *
596 * Emits the 'eos' signal for a stream. To be used by
597 * implementations only.
598 */
599 void
600 _rb_player_emit_eos (RBPlayer *player, gpointer stream_data, gboolean early)
601 {
602 g_signal_emit (player, signals[EOS], 0, stream_data, early);
603 }
604
605 /**
606 * _rb_player_emit_info:
607 * @player: a #RBPlayer implementation
608 * @stream_data: data associated with the stream
609 * @field: updated metadata field
610 * @value: metadata field value
611 *
612 * Emits the 'info' signal for a stream. To be used by
613 * implementations only.
614 */
615 void
616 _rb_player_emit_info (RBPlayer *player,
617 gpointer stream_data,
618 RBMetaDataField field,
619 GValue *value)
620 {
621 g_signal_emit (player, signals[INFO], 0, stream_data, field, value);
622 }
623
624 /**
625 * _rb_player_emit_buffering:
626 * @player: a #RBPlayer implementation
627 * @stream_data: data associated with the stream
628 * @progress: current buffering progress.
629 *
630 * Emits the 'buffering' signal for a stream.
631 * To be used by implementations only.
632 */
633 void
634 _rb_player_emit_buffering (RBPlayer *player, gpointer stream_data, guint progress)
635 {
636 g_signal_emit (player, signals[BUFFERING], 0, stream_data, progress);
637 }
638
639 /**
640 * _rb_player_emit_error:
641 * @player: a #RBPlayer implementation
642 * @stream_data: data associated with the stream
643 * @error: playback error
644 *
645 * Emits the 'error' signal for a stream.
646 * To be used by implementations only.
647 */
648 void
649 _rb_player_emit_error (RBPlayer *player, gpointer stream_data, GError *error)
650 {
651 g_signal_emit (player, signals[ERROR], 0, stream_data, error);
652 }
653
654 /**
655 * _rb_player_emit_tick:
656 * @player: a #RBPlayer implementation
657 * @stream_data: data associated with the stream
658 * @elapsed: current playback position
659 * @duration: current perception of the duration of the stream (-1 if not applicable)
660 *
661 * Emits the 'tick' signal for a stream.
662 * To be used by implementations only.
663 */
664 void
665 _rb_player_emit_tick (RBPlayer *player, gpointer stream_data, gint64 elapsed, gint64 duration)
666 {
667 g_signal_emit (player, signals[TICK], 0, stream_data, elapsed, duration);
668 }
669
670 /**
671 * _rb_player_emit_event:
672 * @player: a #RBPlayer implementation
673 * @stream_data: data associated with the stream
674 * @name: event name
675 * @data: event data
676 *
677 * Emits the 'event' signal for a stream.
678 * To be used by implementations only.
679 */
680 void
681 _rb_player_emit_event (RBPlayer *player, gpointer stream_data, const char *name, gpointer data)
682 {
683 g_signal_emit (player, signals[EVENT], g_quark_from_string (name), stream_data, data);
684 }
685
686 /**
687 * _rb_player_emit_playing_stream:
688 * @player: a #RBPlayer implementation
689 * @stream_data: data associated with the new playing stream
690 *
691 * Emits the 'playing-stream' signal to indicate the current
692 * playing stream has changed. To be used by implementations only.
693 */
694 void
695 _rb_player_emit_playing_stream (RBPlayer *player, gpointer stream_data)
696 {
697 g_signal_emit (player, signals[PLAYING_STREAM], 0, stream_data);
698 }
699
700 /**
701 * _rb_player_emit_volume_changed:
702 * @player: a #RBPlayer implementation
703 * @volume: the new volume level
704 *
705 * Emits the 'volume-changed' signal to indicate the output stream
706 * volume has been changed. To be used by implementations only.
707 */
708 void
709 _rb_player_emit_volume_changed (RBPlayer *player, float volume)
710 {
711 g_signal_emit (player, signals[VOLUME_CHANGED], 0, volume);
712 }
713
714 /**
715 * _rb_player_emit_image:
716 * @player: a #RBPlayer implementation
717 * @stream_data: data associated with the stream
718 * @image: an image extracted from the stream
719 *
720 * Emits the 'image' signal to notify listeners of an image that
721 * has been extracted from the stream. To be used by implementations only.
722 */
723 void
724 _rb_player_emit_image (RBPlayer *player, gpointer stream_data, GdkPixbuf *image)
725 {
726 g_signal_emit (player, signals[IMAGE], 0, stream_data, image);
727 }
728
729 /**
730 * _rb_player_emit_redirect:
731 * @player: a #RBPlayer implementation
732 * @stream_data: data associated with the stream
733 * @uri: URI to redirect to
734 *
735 * Emits the 'redirect' signal to notify listeners that the stream has been
736 * redirected. To be used by implementations only.
737 */
738 void
739 _rb_player_emit_redirect (RBPlayer *player, gpointer stream_data, const char *uri)
740 {
741 g_signal_emit (player, signals[REDIRECT], 0, stream_data, uri);
742 }
743
744 GQuark
745 rb_player_error_quark (void)
746 {
747 static GQuark quark = 0;
748 if (!quark)
749 quark = g_quark_from_static_string ("rb_player_error");
750
751 return quark;
752 }
753
754 /* This should really be standard. */
755 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
756
757 GType
758 rb_player_error_get_type (void)
759 {
760 static GType etype = 0;
761
762 if (etype == 0) {
763 static const GEnumValue values[] = {
764 ENUM_ENTRY (RB_PLAYER_ERROR_NO_AUDIO, "no-audio"),
765 ENUM_ENTRY (RB_PLAYER_ERROR_GENERAL, "general-error"),
766 ENUM_ENTRY (RB_PLAYER_ERROR_INTERNAL, "internal-error"),
767 ENUM_ENTRY (RB_PLAYER_ERROR_NOT_FOUND, "not-found"),
768 { 0, 0, 0 }
769 };
770
771 etype = g_enum_register_static ("RBPlayerError", values);
772 }
773
774 return etype;
775 }
776
777 GType
778 rb_player_play_type_get_type (void)
779 {
780 static GType etype = 0;
781
782 if (etype == 0) {
783 static const GEnumValue values[] = {
784 ENUM_ENTRY (RB_PLAYER_PLAY_REPLACE, "replace"),
785 ENUM_ENTRY (RB_PLAYER_PLAY_AFTER_EOS, "start-after-eos"),
786 ENUM_ENTRY (RB_PLAYER_PLAY_CROSSFADE, "crossfade"),
787 { 0, 0, 0 }
788 };
789
790 etype = g_enum_register_static ("RBPlayerPlayType", values);
791 }
792
793 return etype;
794 }