gboolean GstEnginePipeline::BusCallback(GstBus*, GstMessage* msg, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); qLog(Debug) << instance->id() << "bus message" << GST_MESSAGE_TYPE_NAME(msg); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ERROR: instance->ErrorMessageReceived(msg); break; case GST_MESSAGE_TAG: instance->TagMessageReceived(msg); break; case GST_MESSAGE_STATE_CHANGED: instance->StateChangedMessageReceived(msg); break; default: break; } return FALSE; }
bool GstEnginePipeline::EventHandoffCallback(GstPad*, GstEvent* e, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e); if (GST_EVENT_TYPE(e) == GST_EVENT_NEWSEGMENT && !instance->segment_start_received_) { // The segment start time is used to calculate the proper offset of data // buffers from the start of the stream gint64 start = 0; gst_event_parse_new_segment(e, nullptr, nullptr, nullptr, &start, nullptr, nullptr); instance->segment_start_ = start; instance->segment_start_received_ = true; if (instance->emit_track_ended_on_segment_start_) { qLog(Debug) << "New segment started, EOS will signal on next buffer " "discontinuity"; instance->emit_track_ended_on_segment_start_ = false; instance->emit_track_ended_on_time_discontinuity_ = true; } } return true; }
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo* info, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstEvent* e = gst_pad_probe_info_get_event(info); qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e); if (GST_EVENT_TYPE(e) == GST_EVENT_SEGMENT && !instance->segment_start_received_) { // The segment start time is used to calculate the proper offset of data // buffers from the start of the stream const GstSegment* segment = nullptr; gst_event_parse_segment(e, &segment); instance->segment_start_ = segment->start; instance->segment_start_received_ = true; if (instance->emit_track_ended_on_segment_start_) { qLog(Debug) << "New segment started, EOS will signal on next buffer " "discontinuity"; instance->emit_track_ended_on_segment_start_ = false; instance->emit_track_ended_on_time_discontinuity_ = true; } } return GST_PAD_PROBE_OK; }
void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin, GParamSpec* pspec, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstElement* element; g_object_get(bin, "source", &element, nullptr); if (!element) { return; } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) { // Gstreamer is not able to handle device in URL (refering to Gstreamer // documentation, this might be added in the future). Despite that, for now // we include device inside URL: we decompose it during Init and set device // here, when this callback is called. g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr); } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) { QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()); g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr); #ifdef Q_OS_DARWIN g_object_set(element, "tls-database", instance->engine_->tls_database(), nullptr); g_object_set(element, "ssl-use-system-ca-file", false, nullptr); g_object_set(element, "ssl-strict", TRUE, nullptr); #endif } }
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo* info, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstEvent* e = gst_pad_probe_info_get_event(info); qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e); switch (GST_EVENT_TYPE(e)) { case GST_EVENT_SEGMENT: if (!instance->segment_start_received_) { // The segment start time is used to calculate the proper offset of data // buffers from the start of the stream const GstSegment* segment = nullptr; gst_event_parse_segment(e, &segment); instance->segment_start_ = segment->start; instance->segment_start_received_ = true; } break; default: break; } return GST_PAD_PROBE_OK; }
void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); if (instance->has_next_valid_url()) { instance->TransitionToNext(); } }
bool GstEnginePipeline::HandoffCallback(GstPad*, GstBuffer* buf, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); QList<BufferConsumer*> consumers; { QMutexLocker l(&instance->buffer_consumers_mutex_); consumers = instance->buffer_consumers_; } foreach (BufferConsumer* consumer, consumers) { gst_buffer_ref(buf); consumer->ConsumeBuffer(buf, instance->id()); }
GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); qLog(Debug) << instance->id() << "sync bus message" << GST_MESSAGE_TYPE_NAME(msg); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: emit instance->EndOfStreamReached(instance->id(), false); break; case GST_MESSAGE_TAG: instance->TagMessageReceived(msg); break; case GST_MESSAGE_ERROR: instance->ErrorMessageReceived(msg); break; case GST_MESSAGE_ELEMENT: instance->ElementMessageReceived(msg); break; case GST_MESSAGE_STATE_CHANGED: instance->StateChangedMessageReceived(msg); break; case GST_MESSAGE_BUFFERING: instance->BufferingMessageReceived(msg); break; case GST_MESSAGE_STREAM_STATUS: instance->StreamStatusMessageReceived(msg); break; case GST_MESSAGE_STREAM_START: if (instance->emit_track_ended_on_stream_start_) { qLog(Debug) << "New segment started, EOS will signal on next buffer " "discontinuity"; instance->emit_track_ended_on_stream_start_ = false; instance->emit_track_ended_on_time_discontinuity_ = true; } break; default: break; } return GST_BUS_PASS; }
void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); if (instance->has_next_valid_url() && // I'm not sure why, but calling this when previous track is a local song // and the next track is a Spotify song is buggy: the Spotify song will // not start or with some offset. So just do nothing here: when the song // finished, EndOfStreamReached/TrackEnded will be emitted anyway so // NextItem will be called. !(instance->url_.scheme() != "spotify" && instance->next_url_.scheme() == "spotify")) { instance->TransitionToNext(); } }
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad* pad, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstPad* const audiopad = gst_element_get_pad(instance->audiobin_, "sink"); if (GST_PAD_IS_LINKED(audiopad)) { qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad"; gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); } gst_pad_link(pad, audiopad); gst_object_unref(audiopad); instance->pipeline_is_connected_ = true; if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) { QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_)); } }
GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); qLog(Debug) << instance->id() << "sync bus message" << GST_MESSAGE_TYPE_NAME(msg); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: emit instance->EndOfStreamReached(instance->id(), false); break; case GST_MESSAGE_TAG: instance->TagMessageReceived(msg); break; case GST_MESSAGE_ERROR: instance->ErrorMessageReceived(msg); break; case GST_MESSAGE_ELEMENT: instance->ElementMessageReceived(msg); break; case GST_MESSAGE_STATE_CHANGED: instance->StateChangedMessageReceived(msg); break; case GST_MESSAGE_BUFFERING: instance->BufferingMessageReceived(msg); break; case GST_MESSAGE_STREAM_STATUS: instance->StreamStatusMessageReceived(msg); break; default: break; } return GST_BUS_PASS; }
void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin, GParamSpec* pspec, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstElement* element; g_object_get(bin, "source", &element, nullptr); if (!element) { return; } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) { // Gstreamer is not able to handle device in URL (refering to Gstreamer // documentation, this might be added in the future). Despite that, for now // we include device inside URL: we decompose it during Init and set device // here, when this callback is called. g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr); } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "extra-headers") && instance->url().host().contains("grooveshark")) { // Grooveshark streaming servers will answer with a 400 error 'Bad request' // if we don't specify 'Range' field in HTTP header. // Maybe it could be useful in some other cases, but for now, I prefer to // keep this grooveshark specific. GstStructure* headers; headers = gst_structure_new("extra-headers", "Range", G_TYPE_STRING, "bytes=0-", nullptr); g_object_set(element, "extra-headers", headers, nullptr); gst_structure_free(headers); } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) { QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()); g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr); } }
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad* pad, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); GstPad* const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink"); // Link decodebin's sink pad to audiobin's src pad. if (GST_PAD_IS_LINKED(audiopad)) { qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad"; gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); } gst_pad_link(pad, audiopad); gst_object_unref(audiopad); // Offset the timestamps on all the buffers coming out of the decodebin so // they line up exactly with the end of the last buffer from the old // decodebin. // "Running time" is the time since the last flushing seek. GstClockTime running_time = gst_segment_to_running_time( &instance->last_decodebin_segment_, GST_FORMAT_TIME, instance->last_decodebin_segment_.position); gst_pad_set_offset(pad, running_time); // Add a probe to the pad so we can update last_decodebin_segment_. gst_pad_add_probe( pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), DecodebinProbe, instance, nullptr); instance->pipeline_is_connected_ = true; if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) { QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_)); } }
bool GstEnginePipeline::HandoffCallback(GstPad*, GstBuffer* buf, gpointer self) { GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self); QList<BufferConsumer*> consumers; { QMutexLocker l(&instance->buffer_consumers_mutex_); consumers = instance->buffer_consumers_; } for (BufferConsumer* consumer : consumers) { gst_buffer_ref(buf); consumer->ConsumeBuffer(buf, instance->id()); } // Calculate the end time of this buffer so we can stop playback if it's // after the end time of this song. if (instance->end_offset_nanosec_ > 0) { quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_; quint64 duration = GST_BUFFER_DURATION(buf); quint64 end_time = start_time + duration; if (end_time > instance->end_offset_nanosec_) { if (instance->has_next_valid_url()) { if (instance->next_url_ == instance->url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) { // The "next" song is actually the next segment of this file - so // cheat and keep on playing, but just tell the Engine we've moved on. instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_; instance->next_url_ = QUrl(); instance->next_beginning_offset_nanosec_ = 0; instance->next_end_offset_nanosec_ = 0; // GstEngine will try to seek to the start of the new section, but // we're already there so ignore it. instance->ignore_next_seek_ = true; emit instance->EndOfStreamReached(instance->id(), true); } else { // We have a next song but we can't cheat, so move to it normally. instance->TransitionToNext(); } } else { // There's no next song emit instance->EndOfStreamReached(instance->id(), false); } } } if (instance->emit_track_ended_on_time_discontinuity_) { if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) || GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_) { qLog(Debug) << "Buffer discontinuity - emitting EOS"; instance->emit_track_ended_on_time_discontinuity_ = false; emit instance->EndOfStreamReached(instance->id(), true); } } instance->last_buffer_offset_ = GST_BUFFER_OFFSET(buf); return true; }