Beispiel #1
0
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;
}
Beispiel #2
0
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;
}
Beispiel #6
0
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_));
    }
}
Beispiel #11
0
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_));
    }
}
Beispiel #14
0
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;
}