IceGStreamer/src/modules/streamer.cpp

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