No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * rb-audiocd-plugin.c
4 *
5 * Copyright (C) 2006 James Livingston
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * The Rhythmbox authors hereby grant permission for non-GPL compatible
13 * GStreamer plugins to be used and distributed together with GStreamer
14 * and Rhythmbox. This permission is above and beyond the permissions granted
15 * by the GPL license by which Rhythmbox is covered. If you modify this code
16 * you may extend this exception to your version of the code, but you are not
17 * obligated to do so. If you do not wish to do so, delete this exception
18 * statement from your version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
28 */
29
30 #define __EXTENSIONS__
31
32 #include "config.h"
33
34 #include <string.h> /* For strlen */
35
36 #include <glib.h>
37 #include <glib-object.h>
38 #include <glib/gi18n-lib.h>
39 #include <gmodule.h>
40 #include <gtk/gtk.h>
41
42 #include <gst/gst.h>
43
44 #include "rb-plugin-macros.h"
45 #include "rb-debug.h"
46 #include "rb-shell.h"
47 #include "rb-shell-player.h"
48 #include "rb-dialog.h"
49 #include "rb-removable-media-manager.h"
50 #include "rb-audiocd-source.h"
51 #include "rb-player.h"
52 #include "rb-encoder.h"
53 #include "rb-file-helpers.h"
54
55
56 #define RB_TYPE_AUDIOCD_PLUGIN (rb_audiocd_plugin_get_type ())
57 #define RB_AUDIOCD_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_AUDIOCD_PLUGIN, RBAudioCdPlugin))
58 #define RB_AUDIOCD_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_AUDIOCD_PLUGIN, RBAudioCdPluginClass))
59 #define RB_IS_AUDIOCD_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_AUDIOCD_PLUGIN))
60 #define RB_IS_AUDIOCD_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_AUDIOCD_PLUGIN))
61 #define RB_AUDIOCD_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_AUDIOCD_PLUGIN, RBAudioCdPluginClass))
62
63 typedef struct
64 {
65 PeasExtensionBase parent;
66
67 RBShell *shell;
68 guint ui_merge_id;
69
70 GHashTable *sources;
71 char *playing_uri;
72 } RBAudioCdPlugin;
73
74 typedef struct
75 {
76 PeasExtensionBaseClass parent_class;
77 } RBAudioCdPluginClass;
78
79
80 G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
81 GType rb_audiocd_plugin_get_type (void) G_GNUC_CONST;
82
83 static void rb_audiocd_plugin_init (RBAudioCdPlugin *plugin);
84
85 RB_DEFINE_PLUGIN(RB_TYPE_AUDIOCD_PLUGIN, RBAudioCdPlugin, rb_audiocd_plugin,)
86
87 static void
88 rb_audiocd_plugin_init (RBAudioCdPlugin *plugin)
89 {
90 rb_debug ("RBAudioCdPlugin initialising");
91 }
92
93 static gboolean
94 parse_cdda_uri (const char *uri, char **device, gulong *track)
95 {
96 const char *fragment;
97 const char *device_name;
98
99 if (g_str_has_prefix (uri, "cdda://") == FALSE)
100 return FALSE;
101
102 fragment = g_utf8_strrchr (uri, -1, '#');
103 if (fragment == NULL)
104 return FALSE;
105
106 if (track != NULL) {
107 *track = strtoul (fragment + 1, NULL, 0);
108 }
109
110 if (device != NULL) {
111 device_name = uri + strlen("cdda://");
112 *device = g_malloc0 ((fragment - device_name) + 1);
113 memcpy (*device, device_name, (fragment - device_name));
114 }
115 return TRUE;
116 }
117
118 static void
119 rb_audiocd_plugin_playing_uri_changed_cb (RBShellPlayer *player,
120 const char *uri,
121 RBAudioCdPlugin *plugin)
122 {
123 g_free (plugin->playing_uri);
124 plugin->playing_uri = uri ? g_strdup (uri) : NULL;
125 }
126
127 static void
128 set_source_properties (GstElement *source, const char *uri, gboolean playback_mode)
129 {
130 char *device = NULL;
131 gulong track;
132
133 if (parse_cdda_uri (uri, &device, &track) == FALSE)
134 return;
135
136 g_object_set (source, "device", device, "track", track, NULL);
137 g_free (device);
138
139 if (playback_mode) {
140 /* disable paranoia (if using cdparanoiasrc) and set read speed to 1 */
141 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
142 g_object_set (source, "paranoia-mode", 0, NULL);
143
144 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "read-speed"))
145 g_object_set (source, "read-speed", 1, NULL);
146 } else {
147 /* enable full paranoia; maybe this should be configurable. */
148 /* also, sound-juicer defaults to 8 (scratch) not 0xff (full) here.. */
149 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
150 g_object_set (source, "paranoia-mode", 0xff, NULL);
151
152 /* trick cdparanoiasrc into resetting the device speed in case we've
153 * previously set it to 1 for playback
154 */
155 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "read-speed"))
156 g_object_set (source, "read-speed", 0xffff, NULL);
157 }
158 }
159
160 static void
161 rb_audiocd_plugin_prepare_player_source_cb (RBPlayer *player,
162 const char *stream_uri,
163 GstElement *source,
164 RBAudioCdPlugin *plugin)
165 {
166 set_source_properties (source, stream_uri, TRUE);
167 }
168
169 static void
170 rb_audiocd_plugin_prepare_encoder_source_cb (RBEncoderFactory *factory,
171 const char *stream_uri,
172 GObject *source,
173 RBAudioCdPlugin *plugin)
174 {
175 set_source_properties (GST_ELEMENT (source), stream_uri, FALSE);
176 }
177
178 static gboolean
179 rb_audiocd_plugin_can_reuse_stream_cb (RBPlayer *player,
180 const char *new_uri,
181 const char *stream_uri,
182 GstElement *stream_bin,
183 RBAudioCdPlugin *plugin)
184 {
185 char *new_device = NULL;
186 char *old_device = NULL;
187 gboolean result = FALSE;
188
189 if (parse_cdda_uri (new_uri, &new_device, NULL) &&
190 parse_cdda_uri (stream_uri, &old_device, NULL)) {
191 result = (g_strcmp0 (old_device, new_device) == 0);
192 }
193
194 g_free (new_device);
195 g_free (old_device);
196 return result;
197 }
198
199 static void
200 rb_audiocd_plugin_reuse_stream_cb (RBPlayer *player,
201 const char *new_uri,
202 const char *stream_uri,
203 GstElement *element,
204 RBAudioCdPlugin *plugin)
205 {
206 GstFormat track_format = gst_format_get_by_nick ("track");
207 gulong track;
208 char *device = NULL;
209
210 if (parse_cdda_uri (new_uri, &device, &track) == FALSE) {
211 g_assert_not_reached ();
212 }
213
214 rb_debug ("seeking to track %lu on CD device %s", track, device);
215 g_free (device);
216
217 gst_element_seek (element,
218 1.0, track_format, GST_SEEK_FLAG_FLUSH,
219 GST_SEEK_TYPE_SET, track-1,
220 GST_SEEK_TYPE_NONE, -1);
221 }
222
223 static void
224 rb_audiocd_plugin_source_deleted (RBAudioCdSource *source,
225 RBAudioCdPlugin *plugin)
226 {
227 GVolume *volume;
228
229 g_object_get (source, "volume", &volume, NULL);
230 g_hash_table_remove (plugin->sources, volume);
231 g_object_unref (volume);
232 }
233
234 static RBSource *
235 create_source_cb (RBRemovableMediaManager *rmm,
236 GMount *mount,
237 MPIDDevice *device_info,
238 RBAudioCdPlugin *plugin)
239 {
240 RBSource *source = NULL;
241 GVolume *volume = NULL;
242 RBShell *shell;
243
244 g_object_get (plugin, "object", &shell, NULL);
245
246 if (rb_audiocd_is_mount_audiocd (mount)) {
247
248 volume = g_mount_get_volume (mount);
249 if (volume != NULL) {
250 source = rb_audiocd_source_new (G_OBJECT (plugin), shell, volume);
251 g_object_unref (volume);
252 }
253 }
254
255 if (source != NULL) {
256 g_hash_table_insert (plugin->sources, g_object_ref (volume), g_object_ref (source));
257 g_signal_connect_object (G_OBJECT (source),
258 "deleted", G_CALLBACK (rb_audiocd_plugin_source_deleted),
259 plugin, 0);
260
261 if (plugin->ui_merge_id == 0) {
262 char *filename;
263 GtkUIManager *uimanager;
264
265 g_object_get (shell, "ui-manager", &uimanager, NULL);
266
267 filename = rb_find_plugin_data_file (G_OBJECT (plugin), "audiocd-ui.xml");
268 if (filename != NULL) {
269 plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, filename, NULL);
270 gtk_ui_manager_ensure_update (uimanager);
271 } else {
272 g_warning ("Unable to find file: audiocd-ui.xml");
273 }
274
275 g_free (filename);
276 g_object_unref (uimanager);
277 }
278 }
279
280 g_object_unref (shell);
281 return source;
282 }
283
284 static void
285 impl_activate (PeasActivatable *plugin)
286 {
287 RBAudioCdPlugin *pi = RB_AUDIOCD_PLUGIN (plugin);
288 RBRemovableMediaManager *rmm;
289 gboolean scanned;
290 GObject *shell_player;
291 RBPlayer *player_backend;
292 RBShell *shell;
293
294 pi->sources = g_hash_table_new_full (g_direct_hash,
295 g_direct_equal,
296 g_object_unref,
297 g_object_unref);
298
299 g_object_get (plugin, "object", &shell, NULL);
300 g_object_get (shell, "removable-media-manager", &rmm, NULL);
301
302
303 /* watch for new removable media. use connect_after so
304 * plugins for more specific device types can get in first.
305 */
306 g_signal_connect_after (rmm,
307 "create-source-mount", G_CALLBACK (create_source_cb),
308 pi);
309
310 /* only scan if we're being loaded after the initial scan has been done */
311 g_object_get (G_OBJECT (rmm), "scanned", &scanned, NULL);
312 if (scanned) {
313 rb_removable_media_manager_scan (rmm);
314 }
315
316 g_object_unref (rmm);
317
318 /* player backend hooks: specify the device, limit read speed, and disable paranoia
319 * in source elements, and when changing between tracks on the same CD, just seek
320 * between them, rather than closing and reopening the device.
321 */
322 g_object_get (shell, "shell-player", &shell_player, NULL);
323 g_object_get (shell_player, "player", &player_backend, NULL);
324 if (player_backend) {
325 GObjectClass *klass = G_OBJECT_GET_CLASS (player_backend);
326 if (g_signal_lookup ("prepare-source", G_OBJECT_CLASS_TYPE (klass)) != 0) {
327 g_signal_connect_object (player_backend,
328 "prepare-source",
329 G_CALLBACK (rb_audiocd_plugin_prepare_player_source_cb),
330 plugin, 0);
331 }
332 if (g_signal_lookup ("reuse-stream", G_OBJECT_CLASS_TYPE (klass)) != 0) {
333 g_signal_connect_object (player_backend,
334 "can-reuse-stream",
335 G_CALLBACK (rb_audiocd_plugin_can_reuse_stream_cb),
336 plugin, 0);
337 g_signal_connect_object (player_backend,
338 "reuse-stream",
339 G_CALLBACK (rb_audiocd_plugin_reuse_stream_cb),
340 plugin, 0);
341 }
342 }
343 g_object_unref (shell_player);
344
345 /* encoder hooks: specify the device and set the paranoia level (if available) on
346 * source elements.
347 */
348 g_signal_connect_object (rb_encoder_factory_get (),
349 "prepare-source",
350 G_CALLBACK (rb_audiocd_plugin_prepare_encoder_source_cb),
351 plugin, 0);
352
353 g_signal_connect_object (shell_player, "playing-uri-changed",
354 G_CALLBACK (rb_audiocd_plugin_playing_uri_changed_cb),
355 plugin, 0);
356
357 g_object_unref (shell);
358 }
359
360 static void
361 _delete_cb (GVolume *volume,
362 RBSource *source,
363 RBAudioCdPlugin *plugin)
364 {
365 /* block the source deleted handler so we don't modify the hash table
366 * while iterating it.
367 */
368 g_signal_handlers_block_by_func (source, rb_audiocd_plugin_source_deleted, plugin);
369 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
370 }
371
372 static void
373 impl_deactivate (PeasActivatable *bplugin)
374 {
375 RBAudioCdPlugin *plugin = RB_AUDIOCD_PLUGIN (bplugin);
376 RBRemovableMediaManager *rmm = NULL;
377 GtkUIManager *uimanager = NULL;
378 RBShell *shell;
379
380 g_object_get (plugin, "object", &shell, NULL);
381 g_object_get (shell,
382 "removable-media-manager", &rmm,
383 "ui-manager", &uimanager,
384 NULL);
385 g_signal_handlers_disconnect_by_func (rmm, create_source_cb, plugin);
386
387 g_hash_table_foreach (plugin->sources, (GHFunc)_delete_cb, plugin);
388 g_hash_table_destroy (plugin->sources);
389 plugin->sources = NULL;
390 if (plugin->ui_merge_id) {
391 gtk_ui_manager_remove_ui (uimanager, plugin->ui_merge_id);
392 plugin->ui_merge_id = 0;
393 }
394
395 g_object_unref (uimanager);
396 g_object_unref (rmm);
397 g_object_unref (shell);
398 }
399
400 G_MODULE_EXPORT void
401 peas_register_types (PeasObjectModule *module)
402 {
403 rb_audiocd_plugin_register_type (G_TYPE_MODULE (module));
404 _rb_audiocd_source_register_type (G_TYPE_MODULE (module));
405 peas_object_module_register_extension_type (module,
406 PEAS_TYPE_ACTIVATABLE,
407 RB_TYPE_AUDIOCD_PLUGIN);
408 }