hythmbox-2.98/backends/rb-player.c

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 }