249 lines
7.0 KiB
C
249 lines
7.0 KiB
C
#include <gst/gst.h>
|
|
#include <string.h>
|
|
|
|
typedef struct _MusicPlayer {
|
|
GstElement *Mpipeline;
|
|
GstElement *Msource;
|
|
GstElement *Mconvert;
|
|
GstElement *Msink;
|
|
GstBus *Mbus;
|
|
GMainLoop *Mloop;
|
|
gboolean Malive; /* Whether playing status. */
|
|
gint64 Mduration; /* Music duration time. */
|
|
gdouble Mrate; /* Playing speed. */
|
|
gint64 Mcurrent; /* Current position. */
|
|
GstState Mstate; /* Playing state. */
|
|
} MusicPlayer;
|
|
|
|
static void send_seek_event(MusicPlayer *Mdata) {
|
|
GstEvent *seek_event;
|
|
|
|
/* Create the seek event */
|
|
if(Mdata->Mrate > 0) {
|
|
seek_event = gst_event_new_seek(Mdata->Mrate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH |
|
|
GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, Mdata->Mcurrent, GST_SEEK_TYPE_NONE, 0);
|
|
}
|
|
else {
|
|
seek_event = gst_event_new_seek(Mdata->Mrate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH |
|
|
GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, Mdata->Mcurrent);
|
|
}
|
|
|
|
if(Mdata->Msink == NULL) {
|
|
/* If we have not done so, obtain the sink through which we will send the seek events */
|
|
g_object_get(Mdata->Mpipeline, "music-sink", &Mdata->Msink, NULL);
|
|
}
|
|
|
|
/* Send the event */
|
|
gst_element_send_event(Mdata->Msink, seek_event);
|
|
|
|
g_print(" Rate: %0.1f\n", Mdata->Mrate);
|
|
|
|
return ;
|
|
}
|
|
|
|
static void pad_added_handler(GstElement *src, GstPad *new_pad, MusicPlayer *Mdata) {
|
|
GstPadLinkReturn ret;
|
|
GstCaps *new_pad_caps = NULL;
|
|
GstStructure *new_pad_struct = NULL;
|
|
const gchar *new_pad_type = NULL;
|
|
guint caps_size;
|
|
|
|
/* Get music-convert element sink pad. */
|
|
GstPad *sink_pad = gst_element_get_static_pad(Mdata->Mconvert, "sink");
|
|
if(gst_pad_is_linked(sink_pad)) {
|
|
g_print(" We are already linked. Ignoring.\n");
|
|
goto exit;
|
|
}
|
|
|
|
new_pad_caps = gst_pad_query_caps(new_pad, NULL);
|
|
new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
|
|
caps_size = gst_caps_get_size(new_pad_caps);
|
|
new_pad_type = gst_structure_get_name(new_pad_struct);
|
|
if(!g_str_has_prefix(new_pad_type, "audio/x-raw")) {
|
|
g_print(" It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
|
|
goto exit;
|
|
}
|
|
|
|
/* Link "music-source" to "music-convert". */
|
|
ret = gst_pad_link(new_pad, sink_pad);
|
|
if(GST_PAD_LINK_FAILED(ret)) {
|
|
g_printerr("Error: link pad '%s' to '%s' failed.\n",
|
|
GST_PAD_NAME(new_pad), GST_PAD_NAME(sink_pad));
|
|
g_main_loop_quit(Mdata->Mloop);
|
|
}
|
|
else {
|
|
g_print(" Type: %s\n", new_pad_type);
|
|
g_print(" Size: %d\n", caps_size);
|
|
g_print(" Rate: 1.0\n");
|
|
}
|
|
|
|
if(!gst_element_query_duration(Mdata->Msink, GST_FORMAT_TIME, &Mdata->Mduration))
|
|
g_printerr("Could'nt query duration.\n");
|
|
else
|
|
g_print(" Duration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(Mdata->Mduration));
|
|
|
|
return ;
|
|
|
|
exit:
|
|
if(new_pad_caps != NULL)
|
|
gst_caps_unref(new_pad_caps);
|
|
gst_object_unref(sink_pad);
|
|
}
|
|
|
|
static void message_handler(GstBus *bus, GstMessage *msg, MusicPlayer *Mdata) {
|
|
switch(GST_MESSAGE_TYPE(msg)) {
|
|
case GST_MESSAGE_ERROR: {
|
|
GError *err;
|
|
gchar *debug;
|
|
|
|
gst_message_parse_error(msg, &err, &debug);
|
|
g_printerr("Error: %s\n", err->message);
|
|
g_error_free(err);
|
|
g_free(debug);
|
|
|
|
gst_element_set_state(Mdata->Mpipeline, GST_STATE_READY);
|
|
Mdata->Malive = FALSE;
|
|
g_print(" Status: %s > READY\n", gst_element_state_get_name(Mdata->Mstate));
|
|
g_main_loop_quit(Mdata->Mloop);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS: {
|
|
gst_element_set_state(Mdata->Mpipeline, GST_STATE_READY);
|
|
Mdata->Malive = FALSE;
|
|
g_print(" Status: %s > READY\n", gst_element_state_get_name(Mdata->Mstate));
|
|
g_main_loop_quit(Mdata->Mloop);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_STATE_CHANGED: {
|
|
if(GST_MESSAGE_SRC(msg) == GST_OBJECT(Mdata->Msink)) {
|
|
GstState old_state, new_state, pending_state;
|
|
|
|
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
|
|
Mdata->Malive = (new_state == GST_STATE_PLAYING);
|
|
if(old_state == GST_STATE_PLAYING)
|
|
g_print("\n");
|
|
Mdata->Mstate = new_state;
|
|
g_print(" Status: %s > %s\n", gst_element_state_get_name(old_state),
|
|
gst_element_state_get_name(new_state));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
static gboolean refresh_handler(MusicPlayer *Mdata) {
|
|
static gboolean flag = FALSE;
|
|
|
|
if(!Mdata->Malive)
|
|
return TRUE;
|
|
|
|
/* Get current position. */
|
|
if(!gst_element_query_position(Mdata->Msource, GST_FORMAT_TIME, &Mdata->Mcurrent)) {
|
|
g_printerr("Could'nt query current position.\n");
|
|
}
|
|
|
|
if(Mdata->Mcurrent < Mdata->Mduration) {
|
|
g_print(" Position: %" GST_TIME_FORMAT "\r", GST_TIME_ARGS(Mdata->Mcurrent));
|
|
}
|
|
else {
|
|
g_print("\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if(Mdata->Mcurrent > (Mdata->Mduration / 6) && Mdata->Mcurrent < (Mdata->Mduration/3)) {
|
|
gst_element_seek_simple(Mdata->Msink, GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, Mdata->Mduration / 3);
|
|
g_print("\n");
|
|
flag = TRUE;
|
|
}
|
|
|
|
if(Mdata->Mcurrent > (Mdata->Mduration / 2) && flag) {
|
|
g_print("\n");
|
|
flag = FALSE;
|
|
Mdata->Mrate = 2.0;
|
|
send_seek_event(Mdata);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
MusicPlayer Mdata;
|
|
GstStateChangeReturn ret;
|
|
|
|
if(argc < 2) {
|
|
g_print("Usage: %s '(uri)resource'.\n", argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
/* Initialization. */
|
|
gst_init(&argc, &argv);
|
|
memset(&Mdata, 0, sizeof(Mdata));
|
|
Mdata.Malive = FALSE;
|
|
Mdata.Mrate = 1.0;
|
|
Mdata.Mstate = GST_STATE_NULL;
|
|
|
|
/* Create element and pipeline. */
|
|
Mdata.Msource = gst_element_factory_make("uridecodebin", "music-source");
|
|
Mdata.Mconvert = gst_element_factory_make("audioconvert", "music-convert");
|
|
Mdata.Msink = gst_element_factory_make("autoaudiosink", "music-sink");
|
|
Mdata.Mpipeline = gst_pipeline_new("music-pipeline");
|
|
|
|
if(!Mdata.Mpipeline || !Mdata.Msource || !Mdata.Mconvert || !Mdata.Msink) {
|
|
g_printerr("One or more elements could'nt be created.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Add all element into bin. */
|
|
gst_bin_add_many(GST_BIN(Mdata.Mpipeline), Mdata.Msource, Mdata.Mconvert, Mdata.Msink, NULL);
|
|
/* Link 'music-convert' to 'music-sink'. */
|
|
if(!gst_element_link(Mdata.Mconvert, Mdata.Msink)) {
|
|
g_printerr("'music-convert' could'nt link with 'music-sink\n'");
|
|
gst_object_unref(Mdata.Mpipeline);
|
|
return -1;
|
|
}
|
|
|
|
/* Set the uri to play. */
|
|
g_object_set(Mdata.Msource, "uri", argv[1], NULL);
|
|
|
|
/* Handler event of "pad-added". */
|
|
g_signal_connect(Mdata.Msource, "pad-added", G_CALLBACK(pad_added_handler), &Mdata);
|
|
|
|
/* Set playing state. Start playing. */
|
|
ret = gst_element_set_state(Mdata.Mpipeline, GST_STATE_PLAYING);
|
|
if(ret == GST_STATE_CHANGE_FAILURE) {
|
|
g_printerr("Unable to set pipeline to the playing state.\n");
|
|
gst_object_unref(Mdata.Mpipeline);
|
|
return -1;
|
|
}
|
|
|
|
/* Listen to the bus. */
|
|
Mdata.Mbus = gst_element_get_bus(Mdata.Mpipeline);
|
|
gst_bus_add_signal_watch(Mdata.Mbus);
|
|
|
|
/* Handler 'message' signal. */
|
|
g_signal_connect(Mdata.Mbus, "message", G_CALLBACK(message_handler), &Mdata);
|
|
|
|
/* Main loop. */
|
|
Mdata.Mloop = g_main_loop_new(NULL, FALSE);
|
|
|
|
g_print("Runing...\n");
|
|
g_print("\n");
|
|
g_print("Information\n");
|
|
|
|
g_timeout_add(1, (GSourceFunc)refresh_handler, &Mdata);
|
|
g_main_loop_run(Mdata.Mloop);
|
|
|
|
/* Free resources. */
|
|
g_main_loop_unref(Mdata.Mloop);
|
|
gst_element_set_state(Mdata.Mpipeline, GST_STATE_NULL);
|
|
gst_object_unref(Mdata.Mbus);
|
|
gst_object_unref(Mdata.Mpipeline);
|
|
|
|
g_print("\n");
|
|
g_print("Ending...\n");
|
|
|
|
return 0;
|
|
}
|