Rhythmboxで1曲ループを実現する −4−

今回はフラグとボタンを同期させます。

これが終われば実装は終了です。

ちょっと長いので記事を2回に分けて書こうと思います。

 

見ていくのはrhythmbox-3.0.2/shell/rb-shell-player.h、rhythmbox-3.0.2/shell/rb-shell-player.c、rhythmbox-3.0.2/shell/rb-shell.cの3つです。

 

とりあえずrb-shell-player.cを見ていきましょう。

 

まず気になるのがrb_shell_player_constructed関数です。名前からしてプレイヤーを構成するための重要な関数であることがわかります。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

ここでは以前書き換えたuiファイルにおけるボタンのaction_nameプロパティに言及しているようです。そこでapp.play-repeat1に対する記述を書き加えます。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-repeat1", play_repeat1_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>1", "app.play-repeat1", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

これで1曲ループボタンが押された時にplay_repeat1_action_cbという関数を呼び出すことが出来るようになりました。(ついでにCtrl+1をショートカットに割り当てています)

しかしplay_repeat1_action_cb関数はまだ作成していないので書き加えていきます。

一から書くのは骨が折れるのでplay_repeat_action_cb関数を雛形にしちゃいましょう。

static void
play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;
	rb_debug ("repeat changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	repeat = !repeat;
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

見た限りではボタンの状態を取得してそれを反転させる操作をしています。ここで少々問題になるのが太字で示した部分です。

これらの定義を見てみます。

 

[rb_shell_player_get_playback_state]

gboolean
rb_shell_player_get_playback_state (RBShellPlayer *player,
				    gboolean *shuffle,
				    gboolean *repeat)
{
	int i, j;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
		for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
			if (!strcmp (play_order, state_to_play_order[i][j]))
				goto found;

	g_free (play_order);
	return FALSE;

found:
	if (shuffle != NULL) {
		*shuffle = i > 0;
	}
	if (repeat != NULL) {
		*repeat = j > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order]

static const char* const state_to_play_order[2][2] =
	{{"linear",	"linear-loop"},
	 {"shuffle",	"random-by-age-and-rating"}};

 

rb_shell_player_get_playback_state関数はシャッフルと通常ループの状態をフラグとして得ます。返り値は現在の状態がstate_to_play_orderに登録されているものであればTRUE、そうでなければFALSEです。

state_to_play_orderは見ての通りフラグと文字列とを対応付ける配列です。(なぜわざわざ文字列を使うのかは疑問ですが……)

 

さて、この関数と配列はもちろん1曲ループには対応していませんので同じようなものを1曲ループ用に書き加えねばなりません。大変。

 

とはいえ雛形として元の関数を利用すれば意外に楽ちんです。まずはそれぞれをコピーしてその直後にペーストしましょう。

コピペした方を改変していきます。

 

[rb_play_repeat1_action_cb]

static void
play_repeat1_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean repeat1 = FALSE;
	rb_debug ("repeat1 changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state_loop1 (player, &repeat1);

	repeat1 = !repeat1;
	loop1_flag = repeat1;
	neworder = state_to_play_order_loop1[repeat1 ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

[rb_shell_player_get_playback_state_loop1]

gboolean
rb_shell_player_get_playback_state_loop1 (RBShellPlayer *player,
				    gboolean *repeat1)
{
	int i;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < 2; i++)
		if (!strcmp (play_order, state_to_play_order_loop1[i]))
			goto found;

	g_free (play_order);
	return FALSE;

found:
	if (repeat1 != NULL) {
		*repeat1 = i > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order_loop1]

static const char* const state_to_play_order_loop1[2] =
	{"linear1",	"linear-loop1"};

 

青字の部分が変更点です。"linear1"というのは"linear"と同じ動作をするのですが念の為分けて置きました。蛇足かも?

 

ここまできたらもう一息です。

続きは次回。