totem-plugin-viewer.c 66 KB
Newer Older
1 2 3 4
/* Totem Plugin Viewer
 *
 * Copyright © 2004-2006 Bastien Nocera <hadess@hadess.net>
 * Copyright © 2002 David A. Schleef <ds@schleef.org>
5
 * Copyright © 2006, 2009 Christian Persch
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
19 20
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301  USA.
21 22 23 24 25 26
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
27 28
#include <sys/stat.h>
#include <fcntl.h>
29 30
#include <sys/types.h>
#include <unistd.h>
31
#include <errno.h>
32 33 34

#include <glib.h>
#include <glib/gi18n.h>
35
#include <gio/gio.h>
36
#include <gtk/gtkx.h>
37 38
#include <gtk/gtk.h>

39
#include <gdk/gdk.h>
40
#include <gdk/gdkkeysyms.h>
41

42
#include <totem-pl-parser.h>
43
#include <totem-scrsaver.h>
44 45 46 47 48 49

#include <dbus/dbus-glib.h>

#include "bacon-video-widget.h"
#include "totem-interface.h"
#include "totem-statusbar.h"
50 51
#include "totem-time-label.h"
#include "totem-fullscreen.h"
52
#include "totem-glow-button.h"
53 54
#include "video-utils.h"

55
#include "totem-plugin-viewer-constants.h"
56
#include "totem-plugin-viewer-options.h"
57
#include "marshal.h"
58 59 60

GtkWidget *totem_statusbar_create (void);
GtkWidget *totem_volume_create (void);
61
GtkWidget *totem_pp_create (void);
62

63 64 65 66
/* Private function in totem-pl-parser, not for use
 * by anyone but us */
char * totem_pl_parser_resolve_uri (GFile *base_gfile, const char *relative_uri);

67 68
#define VOLUME_DOWN_OFFSET (-0.08)
#define VOLUME_UP_OFFSET (0.08)
69
#define MINIMUM_VIDEO_SIZE 150
70 71 72 73 74 75 76 77 78 79

/* For newer D-Bus version */
#ifndef DBUS_NAME_FLAG_PROHIBIT_REPLACEMENT
#define DBUS_NAME_FLAG_PROHIBIT_REPLACEMENT 0
#endif

typedef enum {
	TOTEM_PLUGIN_TYPE_GMP,
	TOTEM_PLUGIN_TYPE_NARROWSPACE,
	TOTEM_PLUGIN_TYPE_MULLY,
80
	TOTEM_PLUGIN_TYPE_CONE,
81
	TOTEM_PLUGIN_TYPE_VEGAS,
82 83 84 85 86 87 88 89 90 91 92 93
	TOTEM_PLUGIN_TYPE_LAST
} TotemPluginType;

#define TOTEM_TYPE_EMBEDDED (totem_embedded_get_type ())
#define TOTEM_EMBEDDED(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_EMBEDDED, TotemEmbedded))
#define TOTEM_EMBEDDED_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_EMBEDDED, TotemEmbeddedClass))
#define TOTEM_IS_EMBEDDED(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_EMBEDDED))
#define TOTEM_IS_EMBEDDED_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_EMBEDDED))
#define TOTEM_EMBEDDED_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_EMBEDDED, TotemEmbeddedClass))

typedef GObjectClass TotemEmbeddedClass;

94
typedef struct {
95
	char *uri;
96
	char *subtitle;
97
	char *title;
98
	int duration;
99
	int starttime;
100 101
} TotemPlItem;

102 103 104
typedef struct _TotemEmbedded {
	GObject parent;

Bastien Nocera's avatar
Bastien Nocera committed
105
	DBusGConnection *conn;
106
	GtkWidget *window;
107
	GtkBuilder *menuxml, *xml;
108
	GtkWidget *pp_button;
109
	GtkWidget *pp_fs_button;
110
	TotemStatusbar *statusbar;
111
	TotemScrsaver *scrsaver;
112
	int width, height;
113
        char *user_agent;
114
	const char *mimetype;
115
        char *referrer_uri;
116 117
	char *base_uri;
	char *current_uri;
118
	char *current_subtitle_uri;
119
	char *href_uri;
120
	char *target;
121
	char *stream_uri;
122
	gint64 size; /* the size of the streamed file for fd://0 */
123 124 125 126
	BaconVideoWidget *bvw;
	TotemStates state;
	GdkCursor *cursor;

127
	/* Playlist, a GList of TotemPlItem */
128 129 130
	GList *playlist, *current;
	guint parser_id;
	int num_items;
131
	gboolean remove_copy;
132 133

	/* Open menu item */
134
	GAppInfo *app;
135 136 137 138 139 140
	GtkWidget *menu_item;

	/* Seek bits */
	GtkAdjustment *seekadj;
	GtkWidget *seek;

141 142 143 144 145 146 147
	/* Volume */
	GtkWidget *volume;

	/* Fullscreen */
	TotemFullscreen *fs;
	GtkWidget * fs_window;

148
	/* Error */
149
	GError *error;
150 151 152 153 154 155 156 157 158 159 160

	guint type : 3; /* TotemPluginType */

	guint is_browser_stream : 1;
	guint is_playlist : 1;
	guint controller_hidden : 1;
	guint show_statusbar : 1;
	guint hidden : 1;
	guint repeat : 1;
	guint seeking : 1;
	guint autostart : 1;
161
	guint audioonly : 1;
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
} TotemEmbedded;

GType totem_embedded_get_type (void);

#define TOTEM_EMBEDDED_ERROR_QUARK (g_quark_from_static_string ("TotemEmbeddedErrorQuark"))

enum
{
	TOTEM_EMBEDDED_UNKNOWN_PLUGIN_TYPE,
	TOTEM_EMBEDDED_SETWINDOW_UNSUPPORTED_CONTROLS,
	TOTEM_EMBEDDED_SETWINDOW_HAVE_WINDOW,
	TOTEM_EMBEDDED_SETWINDOW_INVALID_XID,
	TOTEM_EMBEDDED_NO_URI,
	TOTEM_EMBEDDED_OPEN_FAILED,
	TOTEM_EMBEDDED_UNKNOWN_COMMAND
};

G_DEFINE_TYPE (TotemEmbedded, totem_embedded, G_TYPE_OBJECT);
static void totem_embedded_init (TotemEmbedded *emb) { }

static gboolean totem_embedded_do_command (TotemEmbedded *emb, const char *command, GError **err);
static gboolean totem_embedded_push_parser (gpointer data);
static gboolean totem_embedded_play (TotemEmbedded *embedded, GError **error);
185
static void totem_embedded_set_logo_by_name (TotemEmbedded *embedded, const char *name);
186

187
static void totem_embedded_update_menu (TotemEmbedded *emb);
188
static void on_open1_activate (GtkButton *button, TotemEmbedded *emb);
189
static void totem_embedded_toggle_fullscreen (TotemEmbedded *emb);
190

191 192
void on_preferences1_activate (GtkButton *button, TotemEmbedded *emb);
void on_copy_location1_activate (GtkButton *button, TotemEmbedded *emb);
193
void on_fullscreen1_activate (GtkMenuItem *menuitem, TotemEmbedded *emb);
194

195 196
static void update_fill (TotemEmbedded *emb, gdouble level);

197 198 199 200
enum {
	BUTTON_PRESS,
	START_STREAM,
	STOP_STREAM,
201
	SIGNAL_TICK,
202
	PROPERTY_CHANGE,
203 204
	LAST_SIGNAL
};
205

206 207 208 209 210 211 212 213 214 215 216 217 218 219
static int signals[LAST_SIGNAL] = { 0 };

static void
totem_embedded_finalize (GObject *object)
{
	TotemEmbedded *embedded = TOTEM_EMBEDDED (object);

	if (embedded->window)
		gtk_widget_destroy (embedded->window);

	if (embedded->xml)
		g_object_unref (embedded->xml);
	if (embedded->menuxml)
		g_object_unref (embedded->menuxml);
220 221
	if (embedded->fs)
		g_object_unref (embedded->fs);
222 223 224 225 226 227 228 229

	/* FIXME etc */

	G_OBJECT_CLASS (totem_embedded_parent_class)->finalize (object);
}

static void totem_embedded_class_init (TotemEmbeddedClass *klass)
{
230
	GType param_types[3];
231 232 233 234
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = totem_embedded_finalize;

235
	param_types[0] = param_types[1] = G_TYPE_UINT;
236
	param_types[2] = G_TYPE_STRING;
237 238 239 240 241 242
	signals[BUTTON_PRESS] =
		g_signal_newv ("button-press",
				G_TYPE_FROM_CLASS (object_class),
				G_SIGNAL_RUN_LAST,
				NULL /* class closure */,
				NULL /* accu */, NULL /* accu data */,
243 244
				totempluginviewer_marshal_VOID__UINT_UINT,
				G_TYPE_NONE, 2, param_types);
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
	signals[START_STREAM] =
		g_signal_newv ("start-stream",
				G_TYPE_FROM_CLASS (object_class),
				G_SIGNAL_RUN_LAST,
				NULL /* class closure */,
				NULL /* accu */, NULL /* accu data */,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE,
				0, NULL);
	signals[STOP_STREAM] =
		g_signal_newv ("stop-stream",
				G_TYPE_FROM_CLASS (object_class),
				G_SIGNAL_RUN_LAST,
				NULL /* class closure */,
				NULL /* accu */, NULL /* accu data */,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE,
				0, NULL);
263
	signals[SIGNAL_TICK] =
264 265 266 267 268 269 270
		g_signal_newv ("tick",
				G_TYPE_FROM_CLASS (object_class),
				G_SIGNAL_RUN_LAST,
				NULL /* class closure */,
				NULL /* accu */, NULL /* accu data */,
				totempluginviewer_marshal_VOID__UINT_UINT_STRING,
				G_TYPE_NONE, 3, param_types);
271 272 273 274 275 276 277 278
	signals[PROPERTY_CHANGE] =
		g_signal_new ("property-change",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_LAST,
			      0 /* class offset */,
			      NULL /* accu */, NULL /* accu data */,
			      totempluginviewer_marshal_VOID__STRING_BOXED,
			      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VALUE);
279 280 281 282 283 284 285 286 287 288 289 290
}

static void
totem_embedded_exit (TotemEmbedded *emb)
{
	//FIXME what happens when embedded, and we can't go on?
	exit (1);
}

static void
totem_embedded_error_and_exit (char *title, char *reason, TotemEmbedded *emb)
{
Bastien Nocera's avatar
Bastien Nocera committed
291 292 293 294 295 296
	/* Avoid any more contacts, so drop off the bus */
	if (emb->conn != NULL) {
		dbus_g_connection_unregister_g_object(emb->conn, G_OBJECT (emb));
		emb->conn = NULL;
	}

297 298 299 300 301 302
	/* FIXME send a signal to the plugin with the error message instead! */
	totem_interface_error_blocking (title, reason,
			GTK_WINDOW (emb->window));
	totem_embedded_exit (emb);
}

303
static void
304
totem_embedded_volume_changed (TotemEmbedded *emb, double volume)
305
{
306
	GValue value = { 0, };
307

308 309 310 311 312
	g_value_init (&value, G_TYPE_DOUBLE);
	g_value_set_double (&value, volume);
	g_signal_emit (emb, signals[PROPERTY_CHANGE], 0,
		       TOTEM_PROPERTY_VOLUME,
		       &value);
313 314
}

315 316
static void
totem_embedded_set_error (TotemEmbedded *emb,
317
			  int code,
318 319
			  char *secondary)
{
320 321 322
	emb->error = g_error_new_literal (TOTEM_EMBEDDED_ERROR_QUARK,
	                                  code,
	                                  secondary);
323
	g_message ("totem_embedded_set_error: '%s'", secondary);
324 325
}

326 327 328 329 330 331 332 333 334
static gboolean
totem_embedded_set_error_logo (TotemEmbedded *embedded,
			       GError *error)
{
	g_message ("totem_embedded_set_error_logo called by browser plugin");
	totem_embedded_set_logo_by_name (embedded, "image-missing");
	return TRUE;
}

335 336 337 338
static void
totem_embedded_set_state (TotemEmbedded *emb, TotemStates state)
{
	GtkWidget *image;
339
	const gchar *id;
340 341 342 343

	if (state == emb->state)
		return;

344
	g_message ("Viewer state: %s", totem_states[state]);
345

346
	image = gtk_button_get_image (GTK_BUTTON (emb->pp_button));
347 348

	switch (state) {
349
	case TOTEM_STATE_STOPPED:
350
		id = "media-playback-start-symbolic";
351 352
		totem_statusbar_set_text (emb->statusbar, _("Stopped"));
		totem_statusbar_set_time_and_length (emb->statusbar, 0, 0);
353
		totem_time_label_set_time
354
			(TOTEM_TIME_LABEL (emb->fs->time_label), 0, 0);
355
		if (emb->href_uri != NULL && emb->hidden == FALSE) {
356
			gdk_window_set_cursor
357
				(gtk_widget_get_window (GTK_WIDGET (emb->bvw)),
358 359
				 emb->cursor);
		}
360
		break;
361
	case TOTEM_STATE_PAUSED:
362
		id = "media-playback-start-symbolic";
363 364
		totem_statusbar_set_text (emb->statusbar, _("Paused"));
		break;
365
	case TOTEM_STATE_PLAYING:
366
		id = "media-playback-pause-symbolic";
367
		totem_statusbar_set_text (emb->statusbar, _("Playing"));
368
		if (emb->href_uri == NULL && emb->hidden == FALSE) {
369
			gdk_window_set_cursor
370
				(gtk_widget_get_window (GTK_WIDGET (emb->bvw)),
371 372
				 NULL);
		}
373
		break;
374
	case TOTEM_STATE_INVALID:
375
	default:
376
		g_assert_not_reached ();
377 378 379
		break;
	}

380 381
	if (emb->scrsaver != NULL)
		totem_scrsaver_set_state (emb->scrsaver, (state == TOTEM_STATE_PLAYING) ? FALSE : TRUE);
382 383
	gtk_image_set_from_icon_name (GTK_IMAGE (image), id, GTK_ICON_SIZE_MENU);
	gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (emb->pp_fs_button), id);
384

385 386 387
	emb->state = state;
}

388 389 390 391
static void
totem_embedded_set_logo_by_name (TotemEmbedded *embedded,
				 const char *name)
{
392
	totem_embedded_set_state (embedded, TOTEM_STATE_STOPPED);
393

394
	if (embedded->audioonly != FALSE || embedded->hidden != FALSE)
395 396
		return;

397
	bacon_video_widget_set_logo (embedded->bvw, name);
398
	bacon_video_widget_set_logo_mode (embedded->bvw, TRUE);
399 400
}

401 402 403
static void
totem_embedded_set_pp_state (TotemEmbedded *emb, gboolean state)
{
404
	gtk_widget_set_sensitive (emb->pp_button, state);
405
	gtk_widget_set_sensitive (emb->pp_fs_button, state);
406 407 408 409
}

static gboolean
totem_embedded_open_internal (TotemEmbedded *emb,
410
			      gboolean start_play,
411 412 413
			      GError **error)
{
	gboolean retval;
414
	char *uri;
415 416 417

	/* FIXME: stop previous content, or is that automatic ? */

418 419 420 421 422 423 424 425
	if (emb->is_browser_stream) {
		if (emb->size > 0)
			uri = g_strdup_printf ("fd://0?size=%"G_GINT64_FORMAT, emb->size);
		else
			uri = g_strdup ("fd://0");
	} else {
		uri = g_strdup (emb->current_uri);
	}
426 427

	if (!uri) {
428 429 430 431
		g_set_error_literal (error,
                                     TOTEM_EMBEDDED_ERROR_QUARK,
                                     TOTEM_EMBEDDED_NO_URI,
                                     _("No URI to play"));
432 433 434 435 436
		//FIXME totem_embedded_set_error (emb, error); |error| may be null?

		return FALSE;
	}

437 438
	g_message ("totem_embedded_open_internal '%s' subtitle '%s' is-browser-stream %d start-play %d",
		   uri, emb->current_subtitle_uri, emb->is_browser_stream, start_play);
439 440 441

	bacon_video_widget_set_logo_mode (emb->bvw, FALSE);

Bastien Nocera's avatar
Bastien Nocera committed
442
	retval = bacon_video_widget_open (emb->bvw, uri, emb->current_subtitle_uri, NULL);
443 444
	g_free (uri);

Bastien Nocera's avatar
Bastien Nocera committed
445 446 447 448 449
	/* FIXME we shouldn't even do that here */
	if (start_play)
		totem_embedded_play (emb, NULL);
	else
		totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), TRUE);
450

451
	totem_embedded_update_menu (emb);
452 453 454 455
	if (emb->href_uri != NULL)
		totem_fullscreen_set_title (emb->fs, emb->href_uri);
	else
		totem_fullscreen_set_title (emb->fs, emb->current_uri);
456 457 458 459 460 461 462 463

	return retval;
}

static gboolean
totem_embedded_play (TotemEmbedded *emb,
		     GError **error)
{
464 465
	GError *err = NULL;

466 467 468 469 470 471
	if (emb->current_uri == NULL) {
		totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE);
		g_signal_emit (emb, signals[BUTTON_PRESS], 0,
			       gtk_get_current_event_time (), 0);
		return TRUE;
	}
Bastien Nocera's avatar
Bastien Nocera committed
472

473 474
	totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE);

475
	if (bacon_video_widget_play (emb->bvw, &err) != FALSE) {
476
		totem_embedded_set_state (emb, TOTEM_STATE_PLAYING);
477
		totem_embedded_set_pp_state (emb, TRUE);
478 479 480 481
	} else {
		g_warning ("Error in bacon_video_widget_play: %s", err->message);
		g_error_free (err);
	}
482 483 484 485 486 487 488 489

	return TRUE;
}

static gboolean
totem_embedded_pause (TotemEmbedded *emb,
		      GError **error)
{
490
	totem_embedded_set_state (emb, TOTEM_STATE_PAUSED);
491 492 493 494 495 496 497 498 499
	bacon_video_widget_pause (emb->bvw);

	return TRUE;
}

static gboolean
totem_embedded_stop (TotemEmbedded *emb,
		     GError **error)
{
500
	totem_embedded_set_state (emb, TOTEM_STATE_STOPPED);
501 502
	bacon_video_widget_stop (emb->bvw);

503 504 505 506 507
	g_signal_emit (emb, signals[SIGNAL_TICK], 0,
		       (guint32) 0,
		       (guint32) 0,
		       totem_states[emb->state]);

508 509 510 511 512 513 514 515 516 517
	return TRUE;
}

static gboolean
totem_embedded_do_command (TotemEmbedded *embedded,
			   const char *command,
			   GError **error)
{
	g_return_val_if_fail (command != NULL, FALSE);

Bastien Nocera's avatar
Bastien Nocera committed
518 519
	g_message ("totem_embedded_do_command: %s", command);

520 521 522 523 524 525 526 527 528 529 530
	if (strcmp (command, TOTEM_COMMAND_PLAY) == 0) {
		return totem_embedded_play (embedded, error);
	}
	if (strcmp (command, TOTEM_COMMAND_PAUSE) == 0) {
		return totem_embedded_pause (embedded, error);
	}
	if (strcmp (command, TOTEM_COMMAND_STOP) == 0) {
		return totem_embedded_stop (embedded, error);
	}
		
	g_set_error (error,
531 532 533
                     TOTEM_EMBEDDED_ERROR_QUARK,
                     TOTEM_EMBEDDED_UNKNOWN_COMMAND,
                     "Unknown command '%s'", command);
534 535 536
	return FALSE;
}

537
static gboolean
538 539 540 541 542
totem_embedded_set_href (TotemEmbedded *embedded,
			 const char *href_uri,
			 const char *target,
			 GError *error)
{
Bastien Nocera's avatar
Bastien Nocera committed
543 544 545
	g_message ("totem_embedded_set_href %s (target: %s)",
		   href_uri, target);

546 547 548 549 550 551
	g_free (embedded->href_uri);
	g_free (embedded->target);

	if (href_uri != NULL) {
		embedded->href_uri = g_strdup (href_uri);
	} else {
552
		g_free (embedded->href_uri);
553 554
		embedded->href_uri = NULL;
		gdk_window_set_cursor
555
			(gtk_widget_get_window (GTK_WIDGET (embedded->bvw)), NULL);
556 557 558 559 560 561 562
	}

	if (target != NULL) {
		embedded->target = g_strdup (target);
	} else {
		embedded->target = NULL;
	}
563 564

	return TRUE;
565 566
}

Bastien Nocera's avatar
Bastien Nocera committed
567 568 569 570 571 572
static gboolean
totem_embedded_set_volume (TotemEmbedded *embedded,
			   gdouble volume,
			   GError *error)
{
	g_message ("totem_embedded_set_volume: %f", volume);
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
573
	bacon_video_widget_set_volume (embedded->bvw, volume);
574
	totem_embedded_volume_changed (embedded, volume);
Bastien Nocera's avatar
Bastien Nocera committed
575 576 577
	return TRUE;
}

578
static gboolean
579
totem_embedded_launch_player (TotemEmbedded *embedded,
580
			      guint32 user_time)
581
{
582
	GList *uris = NULL;
583 584
	GdkScreen *screen;
	GdkAppLaunchContext *ctx;
585
	gboolean result;
586

587
	g_return_val_if_fail (embedded->app != NULL, FALSE);
588

589
	if (embedded->type == TOTEM_PLUGIN_TYPE_NARROWSPACE
590 591
		   && embedded->href_uri != NULL) {
		uris = g_list_prepend (uris, embedded->href_uri);
592
	} else {
593
		uris = g_list_prepend (uris, embedded->current_uri);
594 595
	}

596 597 598 599 600 601 602 603
	ctx = gdk_display_get_app_launch_context (gtk_widget_get_display (embedded->window));
	screen = gtk_widget_get_screen (embedded->window);
	gdk_app_launch_context_set_screen (ctx, screen);

	gdk_app_launch_context_set_timestamp (ctx, user_time);
	gdk_app_launch_context_set_icon (ctx, g_app_info_get_icon (embedded->app));

	result = g_app_info_launch_uris (embedded->app, uris, G_APP_LAUNCH_CONTEXT (ctx), NULL);
604

605 606
	g_list_free (uris);

607
	return result;
608 609
}

610 611 612 613
static void
totem_embedded_set_uri (TotemEmbedded *emb,
		        const char *uri,
		        const char *base_uri,
614
		        const char *subtitle,
615 616
		        gboolean is_browser_stream)
{
617
	GFile *base_gfile;
618
	char *old_uri, *old_base, *old_href, *old_subtitle;
619

620
	base_gfile = NULL;
621
	old_uri = emb->current_uri;
622
	old_subtitle = emb->current_subtitle_uri;
623
	old_base = emb->base_uri;
624
	old_href = emb->href_uri;
625 626

	emb->base_uri = g_strdup (base_uri);
627 628 629
	if (base_uri)
		base_gfile = g_file_new_for_uri (base_uri);
	emb->current_uri = totem_pl_parser_resolve_uri (base_gfile, uri);
630 631 632 633
	if (subtitle != NULL)
		emb->current_subtitle_uri = totem_pl_parser_resolve_uri (base_gfile, subtitle);
	else
		emb->current_subtitle_uri = NULL;
634 635
	if (base_gfile)
		g_object_unref (base_gfile);
636
	emb->is_browser_stream = (is_browser_stream != FALSE);
637
	emb->href_uri = NULL;
638

639
	if (uri != NULL)
640 641
		g_print ("totem_embedded_set_uri uri %s base %s => resolved %s (subtitle %s => resolved %s)\n",
			 uri, base_uri, emb->current_uri, subtitle, emb->current_subtitle_uri);
642 643
	else
		g_print ("Emptying current_uri\n");
644

645 646 647 648 649 650
	if (old_uri != NULL &&
	    g_str_has_prefix (old_uri, "fd://") != FALSE) {
		int fd;
		fd = strtol (old_uri + strlen ("fd://"), NULL, 0);
		close (fd);
	}
651 652
	g_free (old_uri);
	g_free (old_base);
653
	g_free (old_href);
654
	g_free (old_subtitle);
655 656
	g_free (emb->stream_uri);
	emb->stream_uri = NULL;
657 658
}

659 660 661 662 663 664 665 666 667 668
static void
totem_embedded_update_title (TotemEmbedded *emb, const char *title)
{
	if (title == NULL)
		gtk_window_set_title (GTK_WINDOW (emb->fs_window), _("Totem Movie Player"));
	else
		gtk_window_set_title (GTK_WINDOW (emb->fs_window), title);
	totem_fullscreen_set_title (emb->fs, title);
}

669 670 671 672 673 674 675 676
static void
totem_pl_item_free (gpointer data, gpointer user_data)
{
	TotemPlItem *item = (TotemPlItem *) data;

	if (!item)
		return;
	g_free (item->uri);
677
	g_free (item->title);
678 679
	g_free (item->subtitle);
	g_slice_free (TotemPlItem, item);
680 681 682
}

static gboolean
683
totem_embedded_clear_playlist (TotemEmbedded *emb, GError *error)
684
{
Bastien Nocera's avatar
Bastien Nocera committed
685 686
	g_message ("totem_embedded_clear_playlist");

687 688
	g_list_foreach (emb->playlist, (GFunc) totem_pl_item_free, NULL);
	g_list_free (emb->playlist);
689

690 691 692
	emb->playlist = NULL;
	emb->current = NULL;
	emb->num_items = 0;
693

694
	totem_embedded_set_uri (emb, NULL, NULL, NULL, FALSE);
695

696
	bacon_video_widget_close (emb->bvw);
697
	update_fill (emb, -1.0);
698
	totem_embedded_update_title (emb, NULL);
699 700 701 702 703

	return TRUE;
}

static gboolean
704
totem_embedded_add_item (TotemEmbedded *embedded,
705
			 const char *base_uri,
706 707 708 709
			 const char *uri,
			 const char *title,
			 const char *subtitle,
			 GError *error)
710 711 712
{
	TotemPlItem *item;

Bastien Nocera's avatar
Bastien Nocera committed
713 714
	g_message ("totem_embedded_add_item: %s (base: %s title: %s subtitle: %s)",
		   uri, base_uri, title, subtitle);
715

716
	item = g_slice_new0 (TotemPlItem);
717
	item->uri = g_strdup (uri);
718 719
	item->title = g_strdup (title);
	item->subtitle = g_strdup (subtitle);
720 721 722 723 724 725 726 727 728 729
	item->duration = -1;
	item->starttime = -1;

	embedded->playlist = g_list_append (embedded->playlist, item);
	embedded->num_items++;

	if (embedded->current_uri == NULL) {
		embedded->current = embedded->playlist;
		totem_embedded_set_uri (embedded,
					(const char *) uri,
730
					base_uri,
731
					subtitle,
732 733 734 735 736 737 738
					FALSE);
		totem_embedded_open_internal (embedded, FALSE, NULL /* FIXME */);
	}

	return TRUE;
}

739 740 741 742 743 744 745
static gboolean
totem_embedded_set_fullscreen (TotemEmbedded *emb,
			       gboolean fullscreen_enabled,
			       GError **error)
{
	GValue value = { 0, };
	GtkAction *fs_action;
746 747

	fs_action = GTK_ACTION (gtk_builder_get_object
748 749 750 751 752 753 754 755 756 757 758 759 760 761
				(emb->menuxml, "fullscreen1"));

	if (totem_fullscreen_is_fullscreen (emb->fs) == fullscreen_enabled)
		return TRUE;

	g_message ("totem_embedded_set_fullscreen: %d", fullscreen_enabled);

	if (fullscreen_enabled == FALSE) {
		GtkWidget * container;
		container = GTK_WIDGET (gtk_builder_get_object (emb->xml,
								"video_box"));

		totem_fullscreen_set_fullscreen (emb->fs, FALSE);
		gtk_widget_reparent (GTK_WIDGET (emb->bvw), container);
762
		gtk_widget_hide (emb->fs_window);
763 764 765 766 767 768 769 770 771

		gtk_action_set_sensitive (fs_action, TRUE);
	} else {
		GdkRectangle rect;
		int monitor;

		/* Move the fullscreen window to the screen where the
		 * video widget currently is */
		monitor = gdk_screen_get_monitor_at_window (gtk_widget_get_screen (GTK_WIDGET (emb->bvw)),
772
							    gtk_widget_get_window (GTK_WIDGET (emb->bvw)));
773 774 775
		gdk_screen_get_monitor_geometry (gtk_widget_get_screen (GTK_WIDGET (emb->bvw)),
						 monitor, &rect);
		gtk_window_move (GTK_WINDOW (emb->fs_window), rect.x, rect.y);
776
		totem_interface_set_transient_for (GTK_WINDOW (emb->fs_window), GTK_WINDOW (emb->window));
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795

		gtk_widget_reparent (GTK_WIDGET (emb->bvw), emb->fs_window);
		bacon_video_widget_set_fullscreen (emb->bvw, TRUE);
		gtk_window_fullscreen (GTK_WINDOW (emb->fs_window));
		totem_fullscreen_set_fullscreen (emb->fs, TRUE);
		gtk_widget_show_all (emb->fs_window);

		gtk_action_set_sensitive (fs_action, FALSE);
	}

	g_value_init (&value, G_TYPE_BOOLEAN);
	g_value_set_boolean (&value, fullscreen_enabled);
	g_signal_emit (emb, signals[PROPERTY_CHANGE], 0,
		       TOTEM_PROPERTY_ISFULLSCREEN,
		       &value);

	return TRUE;
}

796 797
static gboolean
totem_embedded_set_time (TotemEmbedded *emb,
798
			 guint64 _time,
799 800
			 GError **error)
{
801
	g_message ("totem_embedded_set_time: %"G_GUINT64_FORMAT, _time);
802

803
	bacon_video_widget_seek_time (emb->bvw, _time, FALSE, NULL);
804 805 806 807

	return TRUE;
}

808 809 810 811 812 813
static gboolean
totem_embedded_open_uri (TotemEmbedded *emb,
			 const char *uri,
			 const char *base_uri,
			 GError **error)
{
814 815
	g_message ("totem_embedded_open_uri: uri %s base_uri: %s", uri, base_uri);

816
	totem_embedded_clear_playlist (emb, NULL);
817

818
	totem_embedded_set_uri (emb, uri, base_uri, NULL, FALSE);
819 820 821
	/* We can only have one item in the "playlist" when
	 * we open a particular URI like this */
	emb->num_items = 1;
822

823
	return totem_embedded_open_internal (emb, TRUE, error);
824 825 826
}

static gboolean
827 828 829 830
totem_embedded_setup_stream (TotemEmbedded *emb,
			     const char *uri,
			     const char *base_uri,
			     GError **error)
831
{
832
	g_message ("totem_embedded_setup_stream called: uri %s, base_uri: %s", uri, base_uri);
833

834
	totem_embedded_clear_playlist (emb, NULL);
835

836
	totem_embedded_set_uri (emb, uri, base_uri, NULL, TRUE);
837 838 839
	/* We can only have one item in the "playlist" when
	 * we open a browser stream */
	emb->num_items = 1;
840 841 842

	/* FIXME: consume any remaining input from stdin */

843 844 845 846 847 848 849 850 851 852 853 854
	return TRUE;
}

static gboolean
totem_embedded_open_stream (TotemEmbedded *emb,
			    gint64 size,
			    GError **error)
{
	g_message ("totem_embedded_open_stream called: with size %"G_GINT64_FORMAT, size);

	emb->size = size;

855
	return totem_embedded_open_internal (emb, TRUE, error);
856 857 858 859 860 861
}

static gboolean
totem_embedded_close_stream (TotemEmbedded *emb,
			     GError *error)
{
Bastien Nocera's avatar
Bastien Nocera committed
862 863
	g_message ("totem_embedded_close_stream");

864 865 866 867 868
	if (!emb->is_browser_stream)
		return TRUE;

	/* FIXME this enough? */
	bacon_video_widget_close (emb->bvw);
869
	update_fill (emb, -1.0);
870 871 872 873 874 875 876 877

	return TRUE;
}

static gboolean
totem_embedded_open_playlist_item (TotemEmbedded *emb,
				   GList *item)
{
878
	TotemPlItem *plitem;
879 880 881 882 883
	gboolean eop;

	if (!emb->playlist)
		return FALSE;

884
	eop = (item == NULL);
885 886 887 888 889 890 891 892 893

	/* Start at the head */
	if (item == NULL)
		item = emb->playlist;

	/* FIXME: if (emb->current == item) { just start again, depending on repeat/autostart settings } */
	emb->current = item;
	g_assert (item != NULL);

894 895
	plitem = (TotemPlItem *) item->data;

896
	totem_embedded_set_uri (emb,
897
				(const char *) plitem->uri,
898
			        emb->base_uri /* FIXME? */,
899
			        plitem->subtitle,
900 901 902
			        FALSE);

	bacon_video_widget_close (emb->bvw);
903 904
	update_fill (emb, -1.0);

905
	//FIXME set the title from the URI if possible
906
	totem_embedded_update_title (emb, plitem->title);
907
	if (totem_embedded_open_internal (emb, FALSE, NULL /* FIXME */)) {
908 909 910 911 912 913
		if (plitem->starttime > 0) {
			gboolean retval;

			g_message ("Seeking to %d seconds for starttime", plitem->starttime);
			retval = bacon_video_widget_seek_time (emb->bvw,
							       plitem->starttime * 1000,
914
							       FALSE,
915 916 917 918 919 920
							       NULL /* FIXME */);
			if (!retval)
				return TRUE;
		}

		if ((eop != FALSE && emb->repeat != FALSE) || (eop == FALSE)) {
921
			    totem_embedded_play (emb, NULL);
922
		}
923 924 925 926 927 928 929 930 931 932 933 934 935 936
	}

	return TRUE;
}

static gboolean
totem_embedded_set_local_file (TotemEmbedded *emb,
			       const char *path,
			       const char *uri,
			       const char *base_uri,
			       GError **error)
{
	char *file_uri;

Bastien Nocera's avatar
Bastien Nocera committed
937 938
	g_message ("Setting the current path to %s (uri: %s base: %s)",
		   path, uri, base_uri);
939

940
	totem_embedded_clear_playlist (emb, NULL);
941 942 943 944 945 946

	file_uri = g_filename_to_uri (path, NULL, error);
	if (!file_uri)
		return FALSE;

	/* FIXME what about |uri| param?!! */
947
	totem_embedded_set_uri (emb, file_uri, base_uri, emb->current_subtitle_uri, FALSE);
948 949
	g_free (file_uri);

950
	return totem_embedded_open_internal (emb, TRUE, error);
951 952
}

953 954 955 956 957
static gboolean
totem_embedded_set_local_cache (TotemEmbedded *emb,
				const char *path,
				GError **error)
{
958
	int fd;
959

Bastien Nocera's avatar
Bastien Nocera committed
960 961
	g_message ("totem_embedded_set_local_cache: %s", path);

962 963 964 965
	/* FIXME Should also handle playlists */
	if (!emb->is_browser_stream)
		return TRUE;

966 967 968 969 970 971
	/* Keep the temporary file open, so that StreamAsFile
	 * doesn't remove it from under us */
	fd = open (path, O_RDONLY);
	if (fd < 0) {
		g_message ("Failed to open local cache file '%s': %s",
			   path, g_strerror (errno));
972
		return FALSE;
973
	}
974

975
	emb->stream_uri = emb->current_uri;
976
	emb->current_uri = g_strdup_printf ("fd://%d", fd);
977 978 979 980

	return TRUE;
}

981 982 983 984 985 986 987
static gboolean
totem_embedded_set_playlist (TotemEmbedded *emb,
			     const char *path,
			     const char *uri,
			     const char *base_uri,
			     GError **error)
{
Bastien Nocera's avatar
Bastien Nocera committed
988 989
	g_message ("Setting the current playlist to %s (uri: %s base: %s)",
		   path, uri, base_uri);
990

991
	totem_embedded_clear_playlist (emb, NULL);
992

993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
	if (path != NULL && *path != '\0') {
		char *file_uri;
		char *tmp_file;
		GError *err = NULL;
		GFile *src, *dst;
		int fd;

		/* FIXME, we should remove that when we can
		 * parse from memory or
		 * https://bugzilla.gnome.org/show_bug.cgi?id=598702 is fixed */
		fd = g_file_open_tmp ("totem-browser-plugin-playlist-XXXXXX",
				      &tmp_file,
				      &err);
		if (fd < 0) {
			g_warning ("Couldn't open temporary file for playlist: %s",
				   err->message);
			g_error_free (err);
			return TRUE;
		}
		src = g_file_new_for_path (path);
		dst = g_file_new_for_path (tmp_file);
		if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE | G_FILE_COPY_TARGET_DEFAULT_PERMS, NULL, NULL, NULL, &err) == FALSE) {
			g_warning ("Failed to copy playlist '%s' to '%s': %s",
				   path, tmp_file, err->message);
			g_error_free (err);
			g_object_unref (src);
			g_object_unref (dst);
			g_free (tmp_file);
			close (fd);
			return TRUE;
		}
1024
		g_free (tmp_file);
1025

1026
		file_uri = g_file_get_uri (dst);
1027

1028 1029 1030
		g_object_unref (src);
		g_object_unref (dst);
		close (fd);
1031

1032
		emb->remove_copy = TRUE;
1033 1034 1035 1036 1037
		totem_embedded_set_uri (emb, file_uri, base_uri, NULL, FALSE);
		g_free (file_uri);
	} else {
		totem_embedded_set_uri (emb, uri, base_uri, NULL, FALSE);
	}
1038 1039 1040 1041 1042 1043 1044 1045 1046

	/* Schedule parsing on idle */
	if (emb->parser_id == 0)
		emb->parser_id = g_idle_add (totem_embedded_push_parser,
					     emb);

	return TRUE;
}

1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
static GAppInfo *
totem_embedded_get_app_for_uri (const char *uri)
{
	char *type;
	GAppInfo *info;

	type = g_content_type_guess (uri, NULL, 0, NULL);
	info = g_app_info_get_default_for_type (type, TRUE);
	g_free (type);

	return info;
}

1060
static void
1061
totem_embedded_update_menu (TotemEmbedded *emb)
1062
{
1063 1064
	GtkWidget *item, *image;
	GtkMenuShell *menu;
1065 1066 1067 1068 1069 1070 1071
	char *label;

	if (emb->menu_item != NULL) {
		gtk_widget_destroy (emb->menu_item);
		emb->menu_item = NULL;
	}
	if (emb->app != NULL) {
1072
		g_object_unref (emb->app);
1073 1074 1075
		emb->app = NULL;
	}

1076
	if (emb->mimetype && strcmp (emb->mimetype, "application/octet-stream") != 0) {
1077
		emb->app = g_app_info_get_default_for_type (emb->mimetype, TRUE);
1078
	} else {
1079 1080 1081 1082 1083 1084 1085 1086
		const char *uri;

		/* If we're reading a local file, that's not a playlist,
		 * we need to use that to get its proper mime-type */
		if (emb->stream_uri != NULL)
			uri = emb->stream_uri;
		else
			uri = emb->current_uri;
1087
		emb->app = totem_embedded_get_app_for_uri (uri);
1088 1089
	}

1090 1091
	if (emb->app == NULL) {
		if (emb->mimetype != NULL) {
1092
			g_message ("Mimetype '%s' doesn't have a handler", emb->mimetype);
1093
		} else {
1094 1095 1096
			char *type;

			type = g_content_type_guess (emb->current_uri, NULL, 0, NULL);
1097
			g_message ("No handler for URI '%s' (guessed mime-type '%s')",
1098 1099
				   emb->current_uri, type);
			g_free (type);
1100
		}
1101
		return;
1102
	}
1103 1104 1105 1106

	/* translators: this is:
	 * Open With ApplicationName
	 * as in nautilus' right-click menu */
1107
	label = g_strdup_printf (_("_Open with \"%s\""), g_app_info_get_name (emb->app));
1108 1109 1110 1111 1112
	item = gtk_image_menu_item_new_with_mnemonic (label);
	g_free (label);
	image = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
	g_signal_connect (G_OBJECT (item), "activate",
1113
			  G_CALLBACK (on_open1_activate), emb);
1114 1115 1116
	gtk_widget_show (item);
	emb->menu_item = item;

1117
	menu = GTK_MENU_SHELL (gtk_builder_get_object (emb->menuxml, "menu"));
1118
	gtk_menu_shell_insert (menu, item, 1);
1119 1120 1121 1122 1123
}

static void
on_open1_activate (GtkButton *button, TotemEmbedded *emb)
{
1124 1125
	GTimeVal val;
	g_get_current_time (&val);
1126
	totem_embedded_launch_player (emb, val.tv_sec);
1127
	totem_embedded_stop (emb, NULL);
1128 1129
}

1130 1131 1132 1133 1134 1135 1136
void
on_fullscreen1_activate (GtkMenuItem *menuitem, TotemEmbedded *emb)
{
	if (totem_fullscreen_is_fullscreen (emb->fs) == FALSE)
		totem_embedded_toggle_fullscreen (emb);
}

1137
void
1138 1139 1140 1141 1142 1143
on_copy_location1_activate (GtkButton *button, TotemEmbedded *emb)
{
	GdkDisplay *display;
	GtkClipboard *clip;
	const char *uri;

1144 1145 1146
	if (emb->stream_uri != NULL) {
		uri = emb->stream_uri;
	} else if (emb->href_uri != NULL) {
1147
		uri = emb->href_uri;
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
	} else {
		uri = emb->current_uri;
	}

	display = gtk_widget_get_display (GTK_WIDGET (emb->window));

	/* Set both the middle-click and the super-paste buffers */
	clip = gtk_clipboard_get_for_display (display,
					      GDK_SELECTION_CLIPBOARD);
	gtk_clipboard_set_text (clip, uri, -1);

	clip = gtk_clipboard_get_for_display (display,
					      GDK_SELECTION_PRIMARY);
	gtk_clipboard_set_text (clip, uri, -1);
}

1164 1165 1166 1167 1168 1169
void
on_preferences1_activate (GtkButton *button, TotemEmbedded *emb)
{
	/* TODO: */
}

1170 1171 1172
static void
on_play_pause (GtkWidget *widget, TotemEmbedded *emb)
{
1173
	if (emb->state == TOTEM_STATE_PLAYING) {
1174 1175
		totem_embedded_pause (emb, NULL);
	} else {
1176
		if (emb->current_uri == NULL) {
1177
			totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE);
1178
			g_signal_emit (emb, signals[BUTTON_PRESS], 0,
1179
				       gtk_get_current_event_time (), 0);
1180 1181 1182
		} else {
			totem_embedded_play (emb, NULL);
		}
1183 1184 1185
	}
}

1186
static void
1187
popup_menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *button)
1188 1189 1190 1191
{
	GtkWidget *widget = GTK_WIDGET (button);
	GtkRequisition menu_req;
	GtkTextDirection direction;
1192
	GtkAllocation allocation;
1193

1194
	gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &menu_req);
1195 1196 1197

	direction = gtk_widget_get_direction (widget);

1198 1199 1200 1201
	gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
	gtk_widget_get_allocation (widget, &allocation);
	*x += allocation.x;
	*y += allocation.y;
1202 1203

	if (direction == GTK_TEXT_DIR_LTR)
1204 1205 1206
		*x += MAX (allocation.width - menu_req.width, 0);
	else if (menu_req.width > allocation.width)
		*x -= menu_req.width - allocation.width;
1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233

	/* This might not work properly if the popup button is right at the
	 * top of the screen, but really, what are the chances */
	*y -= menu_req.height;

	*push_in = FALSE;
}

static void
popup_menu_over_arrow (GtkToggleButton *button,
		       GtkMenu *menu,
		       GdkEventButton *event)
{
	gtk_menu_popup (menu, NULL, NULL,
			(GtkMenuPositionFunc) popup_menu_position_func,
			button,
			event ? event->button : 0,
			event ? event->time : gtk_get_current_event_time ());
}

static void
on_popup_button_toggled (GtkToggleButton *button, TotemEmbedded *emb)
{
	GtkMenu *menu;

	menu = GTK_MENU (gtk_builder_get_object (emb->menuxml, "menu"));

1234
	if (gtk_toggle_button_get_active (button) && !gtk_widget_get_visible (GTK_WIDGET (menu))) {
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
		/* we get here only when the menu is activated by a key
		 * press, so that we can select the first menu item */
		popup_menu_over_arrow (button, menu, NULL);
		gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
	}
}

static gboolean
on_popup_button_button_pressed (GtkToggleButton *button,
				GdkEventButton *event,
				TotemEmbedded *emb)
{
	if (event->button == 1) {
		GtkMenu *menu;

		menu = GTK_MENU (gtk_builder_get_object (emb->menuxml, "menu"));
1251
		if (!gtk_widget_get_visible (GTK_WIDGET (menu))) {
1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
			popup_menu_over_arrow (button, menu, event);
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
		} else {
			gtk_menu_popdown (menu);
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
		}

		return TRUE;
	}

	return FALSE;
}

1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
static void
on_popup_menu_unmap (GtkWidget *menu,
		     TotemEmbedded *emb)
{
	GtkWidget *button;

	button = GTK_WIDGET (gtk_builder_get_object (emb->xml, "popup_button"));
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
}

1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
static char *
resolve_redirect (const char *old_mrl, const char *mrl)
{
	GFile *old_file, *parent, *new_file;
	char *retval;

	/* Get the parent for the current MRL, that's our base */
	old_file = g_file_new_for_uri (old_mrl);
	parent = g_file_get_parent (old_file);
	g_object_unref (old_file);

	/* Resolve the URL */
	new_file = g_file_get_child (parent, mrl);
	g_object_unref (parent);

	retval = g_file_get_uri (new_file);
	g_object_unref (new_file);

	return retval;
}

1296 1297 1298
static void
on_got_redirect (GtkWidget *bvw, const char *mrl, TotemEmbedded *emb)
{
1299
	char *new_uri = NULL;
1300

1301 1302 1303
	g_message ("stream uri: %s", emb->stream_uri ? emb->stream_uri : "(null)");
	g_message ("current uri: %s", emb->current_uri ? emb->current_uri : "(null)");
	g_message ("base uri: %s", emb->base_uri ? emb->base_uri : "(null)");
1304 1305 1306
	g_message ("redirect: %s", mrl);

	bacon_video_widget_close (emb->bvw);
1307
	update_fill (emb, -1.0);
1308

1309 1310 1311
	/* If we don't have a relative URI */
	if (strstr (mrl, "://") != NULL)
		new_uri = NULL;
1312
	/* We are using a local cache, so we resolve against the stream_uri */
1313
	else if (emb->stream_uri)
1314
		new_uri = resolve_redirect (emb->stream_uri, mrl);
1315 1316
	/* We don't have a local cache, so resolve against the URI */
	else if (emb->current_uri)
1317
		new_uri = resolve_redirect (emb->current_uri, mrl);
1318 1319
	/* FIXME: not sure that this is actually correct... */
	else if (emb->base_uri)
1320
		new_uri = resolve_redirect (emb->base_uri, mrl);
1321

1322
	g_message ("Redirecting to '%s'", new_uri ? new_uri : mrl);
1323 1324 1325 1326

	/* FIXME: clear playlist? or replace current entry? or add a new entry? */
	/* FIXME: use totem_embedded_open_uri? */

1327
	totem_embedded_set_uri (emb, new_uri ? new_uri : mrl , emb->base_uri /* FIXME? */, emb->current_subtitle_uri, FALSE);
1328

1329
	totem_embedded_set_state (emb, TOTEM_STATE_STOPPED);
1330

1331
	totem_embedded_open_internal (emb, TRUE, NULL /* FIXME? */);
1332 1333

	g_free (new_uri);
1334 1335
}

1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385
static char *
totem_embedded_get_nice_name_for_stream (BaconVideoWidget *bvw)
{
	char *title, *artist, *retval;
	int tracknum;
	GValue value = { 0, };

	bacon_video_widget_get_metadata (bvw, BVW_INFO_TITLE, &value);
	title = g_value_dup_string (&value);
	g_value_unset (&value);

	if (title == NULL)
		return NULL;

	bacon_video_widget_get_metadata (bvw, BVW_INFO_ARTIST, &value);
	artist = g_value_dup_string (&value);
	g_value_unset (&value);

	if (artist == NULL)
		return title;

	bacon_video_widget_get_metadata (bvw,
					 BVW_INFO_TRACK_NUMBER,
					 &value);
	tracknum = g_value_get_int (&value);

	if (tracknum != 0) {
		retval = g_strdup_printf ("%02d. %s - %s",
				tracknum, artist, title);
	} else {
		retval = g_strdup_printf ("%s - %s", artist, title);
	}
	g_free (artist);
	g_free (title);

	return retval;
}

static void
on_got_metadata (BaconVideoWidget *bvw, TotemEmbedded *emb)
{
	char *title;

	title = totem_embedded_get_nice_name_for_stream (bvw);
	if (title == NULL)
		return;

	totem_embedded_update_title (emb, title);
}

1386 1387 1388 1389
static void
totem_embedded_toggle_fullscreen (TotemEmbedded *emb)
{
	if (totem_fullscreen_is_fullscreen (emb->fs) != FALSE)
1390 1391 1392
		totem_embedded_set_fullscreen (emb, FALSE, NULL);
	else
		totem_embedded_set_fullscreen (emb, TRUE, NULL);
1393 1394 1395 1396 1397 1398 1399 1400 1401
}

static void
totem_embedded_on_fullscreen_exit (GtkWidget *widget, TotemEmbedded *emb)
{
	if (totem_fullscreen_is_fullscreen (emb->fs) != FALSE)
		totem_embedded_toggle_fullscreen (emb);
}

1402 1403 1404 1405 1406 1407
static gboolean
on_video_button_press_event (BaconVideoWidget *bvw,
			     GdkEventButton *event,
			     TotemEmbedded *emb)
{
	guint state = event->state & gtk_accelerator_get_default_mod_mask ();
1408
	GtkMenu *menu;
1409 1410 1411 1412

	//g_print ("button-press type %d button %d state %d play-state %d\n",
	//	 event->type, event->button, state, emb->state);

1413 1414
	menu = GTK_MENU (gtk_builder_get_object (emb->menuxml, "menu"));

1415
	if (event->type == GDK_BUTTON_PRESS && event->button == 1 && state == 0 && emb->state == TOTEM_STATE_STOPPED) {
1416 1417 1418 1419
		if (emb->error != NULL) {
			totem_interface_error (_("An error occurred"), emb->error->message, (GtkWindow *) (emb->window));
			g_error_free (emb->error);
			emb->error = NULL;
1420
		} else if (!gtk_widget_get_visible (GTK_WIDGET (menu))) {
1421 1422 1423 1424