306 lines
11 KiB
C++
306 lines
11 KiB
C++
#include "streamer.h"
|
|
#include "config.h"
|
|
#include "exceptions.h"
|
|
#include <fstream>
|
|
#include <gstreamer-1.0/gst/gst.h>
|
|
#include <list>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
namespace IceGStreamer {
|
|
namespace Streamer {
|
|
|
|
class StreamerInternals;
|
|
|
|
class PlaylistHandler {
|
|
private:
|
|
Config::_config_playlist &config;
|
|
std::ifstream playlist_file;
|
|
|
|
public:
|
|
std::string current_track{};
|
|
|
|
PlaylistHandler(Config::_config_playlist &config) : config(config) {
|
|
playlist_file.open(config.path, std::ios_base::in);
|
|
load_next_track();
|
|
}
|
|
// inline void load_playlist(std::string &path, _playlistType &playlist) {
|
|
// std::ifstream playlist_file(path);
|
|
// if (!playlist_file.is_open())
|
|
// throw Exceptions::PlaylistUnreadableException(path);
|
|
// playlist.clear();
|
|
// std::string line{};
|
|
// while (std::getline(playlist_file, line)) {
|
|
// playlist.push_back(line);
|
|
// }
|
|
// if (playlist.empty())
|
|
// throw Exceptions::PlaylistEmptyException(path);
|
|
// }
|
|
|
|
void reload_playlist() {
|
|
playlist_file.close();
|
|
playlist_file.open(config.path, std::ios_base::in);
|
|
if (!playlist_file.good())
|
|
throw Exceptions::PlaylistUnreadableException(config.path);
|
|
}
|
|
|
|
void load_next_track() {
|
|
std::string line{};
|
|
bool is_eof_reached{false};
|
|
while (true) {
|
|
std::getline(playlist_file, line);
|
|
// std::cout << "LINE IN PLAYLIST: " << line << std::endl;
|
|
if (playlist_file.eof()) {
|
|
if (is_eof_reached)
|
|
throw Exceptions::NoReadablePlaylistItemException(config.path);
|
|
// std::cout << "PLAYLIST EOF, SEEKING TO START" << std::endl;
|
|
playlist_file.clear();
|
|
playlist_file.seekg(0);
|
|
is_eof_reached = true;
|
|
continue;
|
|
}
|
|
if (line[0] == '#' || line.empty())
|
|
continue;
|
|
if (!std::ifstream(line).good()) {
|
|
// std::cout << "FILE UNREADABLE: "
|
|
// << "'" << line << "'" << std::endl;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
current_track = std::move(line);
|
|
std::cout << "NEXT TRACK: " << current_track << std::endl;
|
|
}
|
|
|
|
~PlaylistHandler() { playlist_file.close(); }
|
|
};
|
|
|
|
class StreamerInternals {
|
|
private:
|
|
PlaylistHandler playlist_handler;
|
|
GstElement *pipeline{}, *filesrc{}, *decodebin{}, *convert{}, *sink{};
|
|
GstBus *bus;
|
|
GstMessage *msg;
|
|
GMainLoop *main_loop;
|
|
const char *decodebin_name_{"icegstreamer-decodebin"};
|
|
typedef enum {
|
|
INT_STATUS_UNKNOWN_FILETYPE = std::uint16_t(1 << 0),
|
|
} InternalStatus;
|
|
std::uint16_t internal_status_{0};
|
|
|
|
public:
|
|
Config::_config &config;
|
|
StreamerInternals(Config::_config &config)
|
|
: config(config), playlist_handler(config.playlist) {}
|
|
|
|
static void cb_decodebin_pad_added(
|
|
const GstElement &object, GstPad *pad,
|
|
const StreamerInternals &instance) {
|
|
std::cout << "cb_decodebin_pad_added: " << GST_OBJECT_NAME(pad)
|
|
<< std::endl;
|
|
if (!gst_pad_is_linked(pad)) {
|
|
const GstPadLinkReturn pad_return = gst_pad_link(
|
|
pad, gst_element_get_static_pad(instance.convert, "sink"));
|
|
std::cout << "cb_decodebin_pad_added->link: " << pad_return << std::endl;
|
|
}
|
|
GstCaps *caps = gst_pad_query_caps(pad, nullptr);
|
|
// GstCaps &caps = *gst_pad_get_current_caps(pad);
|
|
for (int idx_caps = 0; idx_caps < gst_caps_get_size(caps); idx_caps++) {
|
|
GstStructure *gst_struct = gst_caps_get_structure(caps, idx_caps);
|
|
std::cout << "cb_decodebin_pad_added->structure(" << idx_caps
|
|
<< "): " << gst_structure_get_name(gst_struct) << std::endl;
|
|
GstCapsFeatures *features = gst_caps_get_features(caps, idx_caps);
|
|
std::cout << "| size: " << gst_caps_features_get_size(features)
|
|
<< std::endl;
|
|
std::cout << "| features: " << gst_caps_features_to_string(features)
|
|
<< std::endl;
|
|
}
|
|
gst_caps_unref(caps);
|
|
}
|
|
|
|
static void cb_decodebin_pad_removed(
|
|
const GstElement &object, const GstPad *pad,
|
|
const StreamerInternals &instance) {
|
|
std::cout << "cb_decodebin_pad_removed: " << GST_OBJECT_NAME(pad)
|
|
<< std::endl;
|
|
// GstStructure *gst_struct = gst_structure_new_empty("test-gst-struct");
|
|
// GstMessage *msg = gst_message_new_application(&object.object,
|
|
// gst_struct); gst_bus_post(instance.bus, msg); printf("BUS MESSAGE
|
|
// POSTED\n");
|
|
}
|
|
|
|
static void cb_decodebin_drained(
|
|
const GstElement &object, const StreamerInternals &instance) {
|
|
std::cout << "cb_decodebin_drained: " << GST_OBJECT_NAME(&object)
|
|
<< std::endl;
|
|
}
|
|
|
|
static void cb_decodebin_no_more_pads(
|
|
const GstElement &object, StreamerInternals &instance) {
|
|
std::cout << "cb_decodebin_no_more_pads: " << GST_OBJECT_NAME(&object)
|
|
<< std::endl;
|
|
}
|
|
|
|
static void cb_decodebin_unknown_type(
|
|
const GstElement &object, const GstPad &pad, const GstCaps &caps,
|
|
StreamerInternals &instance) {
|
|
std::cout << "cb_decodebin_unknown_type: " << GST_OBJECT_NAME(&pad)
|
|
<< std::endl;
|
|
const GstStructure &gststruct = *gst_caps_get_structure(&caps, 0);
|
|
std::cout << "NAME: " << gst_structure_get_name(&gststruct) << std::endl;
|
|
const GstMessage *msg =
|
|
gst_bus_pop_filtered(instance.bus, GST_MESSAGE_ERROR);
|
|
std::cout << "MSG: " << msg << std::endl;
|
|
instance.internal_status_ |= INT_STATUS_UNKNOWN_FILETYPE;
|
|
}
|
|
|
|
void process_bus_message_application(const GstStructure &msg_struct) {
|
|
std::cout << "APP MESSAGE CAUGHT: " << gst_structure_get_name(&msg_struct)
|
|
<< std::endl;
|
|
}
|
|
|
|
void process_bus_unknown_type_error() {
|
|
internal_status_ &= ~INT_STATUS_UNKNOWN_FILETYPE;
|
|
GstStateChangeReturn ret;
|
|
ret = gst_element_set_state(pipeline, GST_STATE_NULL);
|
|
playlist_handler.load_next_track();
|
|
g_object_set(
|
|
filesrc, "location", playlist_handler.current_track.c_str(), nullptr);
|
|
ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
|
// std::cout << "PLAY RESULT:" << ret << std::endl;
|
|
}
|
|
|
|
void throw_on_unhandled_buserror(GstMessage &msg) {
|
|
GError *err;
|
|
gchar *debug;
|
|
gst_message_parse_error(&msg, &err, &debug);
|
|
gst_element_set_state(pipeline, GST_STATE_READY);
|
|
g_main_loop_quit(main_loop);
|
|
const std::string str_message{static_cast<std::string>(err->message)};
|
|
const std::string str_debug{debug};
|
|
g_error_free(err);
|
|
g_free(debug);
|
|
throw Exceptions::PlayerBusException(str_message + ": " + str_debug);
|
|
}
|
|
|
|
static void
|
|
cb_bus_message(GstBus &bus, GstMessage &msg, StreamerInternals &instance) {
|
|
switch (msg.type) {
|
|
case GST_MESSAGE_APPLICATION: {
|
|
instance.process_bus_message_application(
|
|
*gst_message_get_structure(&msg));
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ERROR: {
|
|
std::cout << "GST_MESSAGE_ERROR CAUGHT FROM: " << GST_OBJECT_NAME(msg.src)
|
|
<< std::endl;
|
|
if (*GST_OBJECT_NAME(msg.src) == *instance.decodebin_name_ &&
|
|
instance.internal_status_ & INT_STATUS_UNKNOWN_FILETYPE)
|
|
return instance.process_bus_unknown_type_error();
|
|
instance.throw_on_unhandled_buserror(msg);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS: {
|
|
std::cout << "GST_MESSAGE_EOS CAUGHT" << std::endl;
|
|
gst_element_set_state(instance.pipeline, GST_STATE_READY);
|
|
instance.playlist_handler.load_next_track();
|
|
g_object_set(
|
|
instance.filesrc, "location",
|
|
instance.playlist_handler.current_track.c_str(), nullptr);
|
|
gst_element_set_state(instance.pipeline, GST_STATE_PLAYING);
|
|
// g_main_loop_quit(instance.main_loop);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// void my_test() {
|
|
// GstCaps *caps = gst_caps_new_empty();
|
|
// GstStructure *gst_struct = gst_structure_new_empty("audio/*");
|
|
// gst_structure_set_
|
|
// // g_object_get(G_OBJECT(decodebin), "sink-caps", &result, nullptr);
|
|
// std::cout << "MY_TEST: " << gst_struct << std::endl;
|
|
// // g_free(result);
|
|
// // gst_object_unref(GST_OBJECT(&result));
|
|
// }
|
|
|
|
void start(int &argc, char *argv[]) {
|
|
gst_init(&argc, &argv);
|
|
filesrc = gst_element_factory_make("filesrc", "icegstreamer-filesrc");
|
|
decodebin = gst_element_factory_make("decodebin", decodebin_name_);
|
|
convert = gst_element_factory_make("audioconvert", "icegstreamer-convert");
|
|
sink = gst_element_factory_make("autoaudiosink", "icegstreamer-sink");
|
|
pipeline = gst_pipeline_new("icegstreamer-pipeline");
|
|
if (!pipeline || !filesrc || !decodebin || !convert || !sink)
|
|
throw Exceptions::PlayerSetupException("Couldn't set up pipeline");
|
|
gst_bin_add_many(
|
|
GST_BIN(pipeline), filesrc, decodebin, convert, sink, nullptr);
|
|
if (!gst_element_link_many(filesrc, decodebin, nullptr))
|
|
throw Exceptions::PlayerSetupException("Can't link pipeline elements");
|
|
if (!gst_element_link_many(convert, sink, nullptr))
|
|
throw Exceptions::PlayerSetupException("Can't link pipeline elements");
|
|
g_signal_connect(
|
|
decodebin, "pad-added", G_CALLBACK(cb_decodebin_pad_added), this);
|
|
g_signal_connect(
|
|
decodebin, "pad-removed", G_CALLBACK(cb_decodebin_pad_removed), this);
|
|
g_signal_connect(
|
|
decodebin, "drained", G_CALLBACK(cb_decodebin_drained), this);
|
|
g_signal_connect(
|
|
decodebin, "unknown-type", G_CALLBACK(cb_decodebin_unknown_type), this);
|
|
g_signal_connect(
|
|
decodebin, "no-more-pads", G_CALLBACK(cb_decodebin_no_more_pads), this);
|
|
g_object_set(
|
|
filesrc, "location", playlist_handler.current_track.c_str(), nullptr);
|
|
// my_test();
|
|
const GstStateChangeReturn ret =
|
|
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
throw Exceptions::PlayerSetupException("Unable to set the playing state");
|
|
}
|
|
bus = gst_element_get_bus(pipeline);
|
|
main_loop = g_main_loop_new(nullptr, false);
|
|
gst_bus_add_signal_watch(bus);
|
|
g_signal_connect(bus, "message", G_CALLBACK(cb_bus_message), this);
|
|
g_main_loop_run(main_loop);
|
|
// msg = gst_bus_timed_pop_filtered(
|
|
// bus, GST_CLOCK_TIME_NONE,
|
|
// static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));
|
|
// printf("ret is %i\n", ret);
|
|
// printf("msg is %i\n", msg->type);
|
|
// GError *err;
|
|
// gchar *debug_info;
|
|
// gst_message_parse_error(msg, &err, &debug_info);
|
|
// g_printerr(
|
|
// "Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src),
|
|
// err->message);
|
|
// g_printerr("Debugging information: %s\n", debug_info ? debug_info :
|
|
// "none");
|
|
// // playlist_handler = PlaylistHandler(config.playlist);
|
|
}
|
|
|
|
~StreamerInternals() {
|
|
// if (sink)
|
|
// gst_object_unref(sink);
|
|
// if (filesrc)
|
|
// gst_object_unref(filesrc);
|
|
if (main_loop)
|
|
g_main_loop_unref(main_loop);
|
|
if (bus)
|
|
gst_object_unref(bus);
|
|
if (pipeline) {
|
|
gst_element_set_state(pipeline, GST_STATE_NULL);
|
|
gst_object_unref(pipeline);
|
|
}
|
|
}
|
|
};
|
|
|
|
void start(int &argc, char *argv[], Config::_config &config) {
|
|
StreamerInternals streamer_class(config);
|
|
streamer_class.start(argc, argv);
|
|
};
|
|
|
|
} // namespace Streamer
|
|
} // namespace IceGStreamer
|