/* * Copyright 2013 (c) Anna Schumaker. */ #include #include #include #include static const char *SETTINGS_TRACK = "core.audio.cur"; static const char *SETTINGS_VOLUME = "core.audio.volume"; static struct file audio_file = FILE_INIT("cur_track", 0); static struct track *audio_track = NULL; static GstElement *audio_player = NULL; static struct audio_callbacks *audio_cb = NULL; static int audio_pause_count = -1; static guint audio_bus = 0; static bool __audio_change_state(GstState state) { GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE; if (audio_cur_state() != state) ret = gst_element_set_state(audio_player, state); if (ret == GST_STATE_CHANGE_FAILURE) return false; if (audio_cb) audio_cb->audio_cb_state_change(state); return true; } static void __audio_gst_load(struct track *track, GstState state) { gchar *path = track_path(track); gchar *uri = gst_filename_to_uri(path, NULL); audio_track = track; gst_element_set_state(audio_player, GST_STATE_NULL); g_object_set(G_OBJECT(audio_player), "uri", uri, NULL); __audio_change_state(state); if (audio_cb) audio_cb->audio_cb_load(track); audio_save(); g_free(uri); g_free(path); } /* Load a track, but don't add it to the history. */ static struct track *__audio_do_load(struct track *track, GstState state) { struct track *prev = audio_track; if (track) { __audio_gst_load(track, state); playlist_selected(track); } playlist_played(prev); return audio_track; } static struct track *__audio_load(struct track *track, GstState state) { struct track *ret = __audio_do_load(track, state); if (ret == track) playlist_add(playlist_lookup(PL_SYSTEM, "History"), ret); return ret; } static struct track *__audio_next(GstState state) { return __audio_load(playlist_next(), state); } static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data) { switch (GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_ERROR: audio_error(message); break; case GST_MESSAGE_EOS: audio_eos(); default: break; } return true; } static bool __audio_init_idle(void *data) { unsigned int track; if (settings_has(SETTINGS_TRACK)) { track = settings_get(SETTINGS_TRACK); __audio_load(track_get(track), GST_STATE_PAUSED); } else if (file_open(&audio_file, OPEN_READ)) { file_readf(&audio_file, "%u", &track); file_close(&audio_file); file_remove(&audio_file); __audio_load(track_get(track), GST_STATE_PAUSED); } return true; } void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks) { unsigned int volume = 100; GstBus *bus; gst_init(argc, argv); audio_player = gst_element_factory_make("playbin", "ocarina_player"); audio_cb = callbacks; bus = gst_pipeline_get_bus(GST_PIPELINE(audio_player)); audio_bus = gst_bus_add_watch(bus, __audio_message, NULL); gst_object_unref(bus); if (settings_has(SETTINGS_VOLUME)) volume = settings_get(SETTINGS_VOLUME); audio_set_volume(volume); idle_schedule(IDLE_SYNC, __audio_init_idle, NULL); } void audio_deinit() { gst_element_set_state(audio_player, GST_STATE_NULL); gst_object_unref(GST_ELEMENT(audio_player)); g_source_remove(audio_bus); audio_player = NULL; audio_track = NULL; gst_deinit(); } void audio_save() { if (audio_track) settings_set(SETTINGS_TRACK, track_index(audio_track)); } bool audio_load(struct track *track) { if (track == audio_track) return false; return __audio_load(track, GST_STATE_PLAYING) == track; } struct track *audio_cur_track() { return audio_track; } GstState audio_cur_state() { GstState cur = GST_STATE_NULL; if (audio_player) gst_element_get_state(audio_player, &cur, NULL, GST_CLOCK_TIME_NONE); return cur; } void audio_set_volume(unsigned int volume) { gdouble vol; if (volume > 100) volume = 100; vol = (gdouble)volume / 100; settings_set(SETTINGS_VOLUME, volume); g_object_set(G_OBJECT(audio_player), "volume", vol, NULL); } unsigned int audio_get_volume() { gdouble volume; g_object_get(G_OBJECT(audio_player), "volume", &volume, NULL); return volume * 100; } bool audio_play() { if (!audio_track) return false; return __audio_change_state(GST_STATE_PLAYING); } bool audio_pause() { if (!audio_track) return false; return __audio_change_state(GST_STATE_PAUSED); } bool audio_seek(gint64 offset) { if (!audio_track) return false; return gst_element_seek_simple(audio_player, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, offset); } gint64 audio_position() { gint64 position; if (gst_element_query_position(audio_player, GST_FORMAT_TIME, &position)) return position; return 0; } int64_t audio_duration() { gint64 duration; if (gst_element_query_duration(audio_player, GST_FORMAT_TIME, &duration)) return duration; if (audio_track) return audio_track->tr_length * GST_SECOND; return 0; } struct track *audio_next() { return __audio_next(GST_STATE_PLAYING); } struct track *audio_prev() { return __audio_do_load(playlist_prev(), GST_STATE_PLAYING); } struct track *audio_eos() { /* Mark current track as played */ if (audio_track) track_played(audio_track); /* Check pause count and pick the next track */ if (audio_pause_count >= 0) { audio_pause_after(audio_pause_count - 1); if (audio_pause_count == -1) return __audio_next(GST_STATE_PAUSED); } return __audio_next(GST_STATE_PLAYING); } void audio_error(GstMessage *error) { gchar *path = NULL, *debug = NULL; GError *err = NULL; if (audio_track) path = track_path(audio_track); if (error) gst_message_parse_error(error, &err, &debug); g_print("Error: %s (%s)\n", err->message, path); if (debug) g_print("Debug details: %s\n", debug); __audio_next(audio_cur_state()); g_error_free(err); g_free(debug); g_free(path); } void audio_pause_after(int n) { if (n != audio_pause_count) { audio_pause_count = n; if (audio_cb) audio_cb->audio_cb_config_pause(audio_pause_count); } } #ifdef CONFIG_TESTING GstElement *test_audio_player() { return audio_player; } #endif /* CONFIG_TESTING */