/* * Copyright 2013 (c) Anna Schumaker. */ #include #include #include #include #define LOAD_PLAYING (1 << 0) /* Begin playback after loading */ #define LOAD_HISTORY (1 << 1) /* Add the track to the history */ #define LOAD_DEFAULT (LOAD_PLAYING | LOAD_HISTORY) static const char *SETTINGS_TRACK = "core.audio.cur"; static const char *SETTINGS_VOLUME = "core.audio.volume"; static struct file audio_file = FILE_INIT_DATA("", "cur_track", 0); static struct track *audio_track = NULL; static int audio_pause_count = -1; static GstElement *audio_pipeline = NULL; static GstElement *audio_source = NULL; static GstElement *audio_decoder = NULL; static GstElement *audio_converter = NULL; static GstElement *audio_volume = NULL; static GstElement *audio_sink = NULL; static guint audio_bus_id = 0; static struct audio_callbacks *audio_cb = NULL; static bool __audio_change_state(GstState state) { if (audio_cur_state() == state) return false; return gst_element_set_state(audio_pipeline, state) != GST_STATE_CHANGE_FAILURE; } static struct track *__audio_load(struct track *track, unsigned int flags) { struct track *prev = audio_track; gchar *path; if (!track) return NULL; audio_track = track; path = track_path(track); if (audio_cur_state() != GST_STATE_NULL) gst_element_set_state(audio_pipeline, GST_STATE_READY); g_object_set(G_OBJECT(audio_source), "location", path, NULL); gst_element_set_state(audio_pipeline, flags & LOAD_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED); playlist_played(prev); if (prev && TRACK_IS_EXTERNAL(prev)) track_free_external(prev); playlist_selected(track); if (flags & LOAD_HISTORY && !TRACK_IS_EXTERNAL(track)) playlist_add(playlist_lookup(PL_SYSTEM, "History"), track); if (audio_cb) audio_cb->audio_cb_load(track); audio_save(); g_free(path); return track; } static void __audio_pad_added(GstElement *element, GstPad *pad, gpointer data) { GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink"); gst_element_link(element, audio_converter); gst_pad_link(pad, sink); gst_object_unref(sink); } static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data) { GstObject *source = GST_OBJECT(GST_MESSAGE_SRC(message)); gchar *debug = NULL; GError *error = NULL; GstState old, state, next; unsigned int load_flags = LOAD_DEFAULT; switch (GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_ERROR: gst_message_parse_error(message, &error, &debug); g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(source), error->message); g_printerr("DEBUG details: %s\n", debug ? debug : "none"); g_error_free(error); g_free(debug); if (audio_cur_state() != GST_STATE_PLAYING) load_flags = LOAD_HISTORY; __audio_load(playlist_next(), load_flags); break; case GST_MESSAGE_EOS: track_played(audio_track); if (audio_pause_count >= 0) { audio_pause_after(audio_pause_count - 1); if (audio_pause_count == -1) load_flags = LOAD_HISTORY; } __audio_load(playlist_next(), load_flags); break; case GST_MESSAGE_STATE_CHANGED: if (!audio_cb || source != GST_OBJECT(audio_pipeline)) break; gst_message_parse_state_changed(message, &old, &state, &next); if (state == GST_STATE_PLAYING || state == GST_STATE_PAUSED) { if (next == GST_STATE_VOID_PENDING) audio_cb->audio_cb_state_change(state); } 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), LOAD_HISTORY); } else if (file_open(&audio_file, OPEN_READ)) { track = file_readu(&audio_file); file_close(&audio_file); file_remove(&audio_file); __audio_load(track_get(track), LOAD_HISTORY); } return true; } void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks) { unsigned int volume = 100; GstBus *bus; gst_init(argc, argv); audio_cb = callbacks; audio_pipeline = gst_pipeline_new("pipeline"); audio_source = gst_element_factory_make("filesrc", "source"); audio_decoder = gst_element_factory_make("decodebin", "decoder"); audio_converter = gst_element_factory_make("audioconvert", "converter"); audio_volume = gst_element_factory_make("volume", "volume"); audio_sink = gst_element_factory_make("autoaudiosink", "sink"); bus = gst_pipeline_get_bus(GST_PIPELINE(audio_pipeline)); audio_bus_id = gst_bus_add_watch(bus, __audio_message, NULL); gst_bin_add_many(GST_BIN(audio_pipeline), audio_source, audio_decoder, audio_converter, audio_volume, audio_sink, NULL); gst_element_link(audio_source, audio_decoder); gst_element_link_many(audio_converter, audio_volume, audio_sink, NULL); g_signal_connect(audio_decoder, "pad-added", G_CALLBACK(__audio_pad_added), 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_pipeline, GST_STATE_NULL); gst_object_unref(GST_ELEMENT(audio_pipeline)); g_source_remove(audio_bus_id); audio_pipeline = NULL; audio_source = NULL; audio_decoder = NULL; audio_converter = NULL; audio_volume = NULL; audio_sink = NULL; audio_track = NULL; gst_deinit(); } void audio_save() { if (audio_track && !TRACK_IS_EXTERNAL(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, LOAD_DEFAULT) != NULL; } bool audio_load_filepath(const gchar *filepath) { struct track *track; if (!filepath) return false; track = track_lookup(filepath); if (!track) track = track_alloc_external(filepath); return audio_load(track); } struct track *audio_cur_track() { return audio_track; } GstState audio_cur_state() { GstState cur = GST_STATE_NULL; if (audio_pipeline) gst_element_get_state(audio_pipeline, &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_volume), "volume", vol, NULL); } unsigned int audio_get_volume() { gdouble volume; g_object_get(G_OBJECT(audio_volume), "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_pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, offset); } gint64 audio_position() { gint64 position; if (gst_element_query_position(audio_pipeline, GST_FORMAT_TIME, &position)) return position; return 0; } gint64 audio_duration() { gint64 duration; if (gst_element_query_duration(audio_pipeline, GST_FORMAT_TIME, &duration)) return duration; if (audio_track) return audio_track->tr_length * GST_SECOND; return 0; } struct track *audio_next() { return __audio_load(playlist_next(), LOAD_DEFAULT); } struct track *audio_prev() { return __audio_load(playlist_prev(), LOAD_PLAYING); } bool audio_pause_after(int n) { if (n >= -1 && n != audio_pause_count) { audio_pause_count = n; if (audio_cb) audio_cb->audio_cb_config_pause(audio_pause_count); return true; } return false; } int audio_get_pause_count(void) { return audio_pause_count; } #ifdef CONFIG_TESTING void test_audio_eos() { GstMessage *message = gst_message_new_eos(GST_OBJECT(audio_pipeline)); __audio_message(NULL, message, NULL); gst_message_unref(message); } void test_audio_error(GError *error, gchar *debug) { GstMessage *message = gst_message_new_error( GST_OBJECT(audio_pipeline), error, debug); __audio_message(NULL, message, NULL); gst_message_unref(message); } GstElement *test_audio_pipeline() { return audio_pipeline; } #endif /* CONFIG_TESTING */